在前面一篇給出的Transaction的定義中,信息的讀者應該看到了一個叫做DepedentClone的方法。該 方法對用於創建基於現有Transaction對象的“依賴事務(DependentTransaction)”。不像可提交事務 是一個獨立的事務對象,依賴事務依附於現有的某個事務(可能是可提交事務,也可能是依賴事務)。 依賴事務可以幫助我們很容易地編寫一些事務型操作,當環境事務不存的時候,可以確保操作在一個獨 立的事務中執行;當環境事務存在的時候,則自動加入其中。
一、依賴事務(Dependent Transaction)
依賴事務通過DependentTransaction類型表示,DependentTransaction定義如下。和 CommittableTransaction一樣,DependentTransaction也是Transaction的子類。既然 DependentTransaction依賴於現有的Transaction對象而存在,相當於被依賴事務的子事務,所以無法執 行對事務的提交,也自然不會定義Commit方法。但是,DependentTransaction具有一個唯一的方法成員 :Complete。調用這個方法意味著向被依賴事務發送通知,表明所有與依賴事務相關的操作已經完成。
1: [Serializable]
2: public sealed class DependentTransaction : Transaction
3: {
4: public void Complete();
5: }
1、通過DependentTransaction將異步操所納入現有事務
通過Transaction的靜態屬性Current表示的環境事務保存在TLS(Thread Local Storage)中,所以 環境事務是基於當前線程的。這就意味著,即使環境事務存在,通過異步調用的操作也不可能自動加入 到當前事務之中,因為在異步線程中感知不到環境事務的存在。在這種情況下,我們需要做的就是手工 將當前事務傳遞到另一個線程中,作為它的環境事務。通過依賴事務我們很容易實現這一點。
DependentTransaction通過Transaction的DependentClone方法創建,該方法具有一個 DependentCloneOption枚舉類型的參數,體現了被依賴的事務再上尚未接受到依賴事務的通知(調用 Complete或者Rollback方法)得情況下,提交或者完成所采取的事務控制行為。DependentCloneOption 提供了兩個選項,BlockCommitUntilComplete表示被依賴事務會一直等待接收到依賴事務的通知或者超 過事務設定的超時時限;而RollbackIfNotComplete則會直接將被依賴的事務回滾,並拋出 TransactionAbortedException異常。
1: [Serializable]
2: public class Transaction : IDisposable, ISerializable
3: {
4: //其他成員
5: public DependentTransaction DependentClone(DependentCloneOption cloneOption);
6: }
7: public enum DependentCloneOption
8: {
9: BlockCommitUntilComplete,
10: RollbackIfNotComplete
11: }
下面的代碼演示了如果通過依賴事務,采用異步的方式進行銀行轉賬操作。借助於組件ThreadPool, 將主線程環境事務的依賴事務傳遞給異步操作代理,開始異步操作的時候將此依賴事務作為當前的環境 事務,那麼之後的操作將自動在當前事務下進行。
1: private static void Transfer(string accountFrom, string accountTo, double amount)
2: {
3: Transaction originalTransaction = Transaction.Current;
4: CommittableTransaction transaction = new CommittableTransaction();
5: try
6: {
7: Transaction.Current = transaction;
8: ThreadPool.QueueUserWorkItem (state =>
9: {
10: Transaction.Current = state as DependentTransaction;
11: try
12: {
13: Withdraw(accountFrom, amount);
14: Deposite(accountTo, amount);
15: (state as DependentTransaction).Complete();
16: }
17: catch (Exception ex)
18: {
19: Transaction.Current.Rollback (ex);
20: }
21: finally
22: {
23: (state as IDisposable).Dispose();
24: Transaction.Current = null;
25: }
26: }, Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete));
27: //其他操作
28: transaction.Commit();
29: }
30: catch (TransactionAbortedException ex)
31: {
32: transaction.Rollback(ex);
33: Console.WriteLine("轉帳失敗,錯 誤信息:{0}", ex.InnerException.Message);
34: }
35: catch (Exception ex)
36: {
37: transaction.Rollback(ex);
38: throw;
39: }
40: finally
41: {
42: Transaction.Current = originalTransaction;
43: transaction.Dispose();
44: }
45: }
由於在調用DependentClone方法創建依賴事務時指定的參數為 DependentCloneOption.BlockCommitUntilComplete,所以主線程在調用Commit方法提交事務的時候,由 於依賴事務尚未結束(調用Complete或者Rollback方法),在這裡會一直等待。如果依賴事務的 Complete或者Rollback一直沒有調用,那麼被依賴的事務會一直等到超出事務設置的超時時限。所以, 對於基於BlockCommitUntilComplete選項創建的依賴事務來說,應該及時地調用Complete或者Rollback 方法。
2、通過DependentTransaction實現事務型方法
這裡所說的事務型方法是指方法的執行總是在事務中執行。具體來講,有兩種不同的事務應用場景: 如果當前不存在環境事務,那麼方法的執行將在一個獨立的事務中執行;反之,如果存在環境事務,在 方法執行會自動加入到環境事務之中。
比如說,存儲(Deposit)和提取(Withdraw)就是典型的事務型操作。對於單純的存取款的場景, 應該創建一個新的事務來控制存儲和提取操作的執行,以確保單一帳戶款項的數據一致性。如果在轉賬 的場景中,應在在轉賬開始之前就創建一個新的事務,讓提取和存儲的操作自動加入到這個事務之中。
我們現在就結合可提交事務和依賴事務將Deposit和Withdraw兩個方法定義成事務型方法,為了相同 代碼的重復,在這裡把事務控制部分定義在如下一個InvokeInTransaction靜態方法中:
1: static void InvokeInTransaction(Action action)
2: {
3: Transaction originalTransaction = Transaction.Current;
4: CommittableTransaction committableTransaction = null;
5: DependentTransaction dependentTransaction = null;
6: if (null == Transaction.Current)
7: {
8: committableTransaction = new CommittableTransaction();
9: Transaction.Current = committableTransaction;
10: }
11: else
12: {
13: dependentTransaction = Transaction.Current.DependentClone (DependentCloneOption.RollbackIfNotComplete);
14: Transaction.Current = dependentTransaction;
15: }
16:
17: try
18: {
19: action();
20: if (null != committableTransaction)
21: {
22: committableTransaction.Commit();
23: }
24:
25: if (null != dependentTransaction)
26: {
27: dependentTransaction.Complete();
28: }
29: }
30: catch (Exception ex)
31: {
32: Transaction.Current.Rollback(ex);
33: throw;
34: }
35: finally
36: {
37: Transaction transaction = Transaction.Current;
38: Transaction.Current = originalTransaction;
39: transaction.Dispose();
40: }
41: }
InvokeInTransaction方法的參數是一個Action類型的代理(Delegate),表示具體的業務操作。在 開始的時候記錄下當前的環境事務,當整個操作結束之後應該環境事務恢復成該值。如果存在環境事務 ,則創建環境事務的依賴事務,反之直接創建可提交事務。並將新創建的依賴事務或者可提交事務作為 當前的環境事務。將目標操作的執行(action)放在try/catch中,當目標操作順利執行後,調用依賴事 務的Complete 方法或者可提交事務的Commit方法。如果拋出異常,則調用環境事務的Rollback進行回滾 。在finally塊中將環境事務恢復到之前的狀態,並調用Dispose方法對創建的事務進行回收。
借助於InvokeInTransaction這個輔助方法,我們以事務型方法的形式定義了如下的兩個方法: Withdraw和Deposit,分別實現提取和存儲的操作。
1: static void Withdraw(string accountId, double amount)
2: {
3: Dictionary<string, object> parameters = new Dictionary<string, object>();
4: parameters.Add("id", accountId);
5: parameters.Add("amount", amount);
6: InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_WITHDRAW", parameters));
7: }
8:
9: static void Deposit(string accountId, double amount)
10: {
11: Dictionary<string, object> parameters = new Dictionary<string, object> ();
12: parameters.Add("id", accountId);
13: parameters.Add ("amount", amount);
14: InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_DEPOSIT", parameters));
15: }
二、TransactionScope
在上面一節,我結合可提交事務和依賴事務,以及環境事務的機制提供了對事務型操作的實現。實際 上,如果借助TransactionScope,相應的代碼將會變得非常簡單。下面的代碼中,通過 TransactionScope對InvokeInTransaction進行了改寫,從執行效果來看這和原來的代碼完全一致。
1: static void InvokeInTransaction(Action action)
2: {
3: using (TransactionScope transactionScope = new TransactionScope())
4: {
5: action();
6: transactionScope.Complete ();
7: }
8: }
通過InvokeInTransaction方法前後代碼的對比,我們可以明顯看到TransactionScope確實能夠使我 們的事務控制變得非常的簡單。實際上,在利用System.Transactions事務進行編程的時候,我們一般不 會使用到可提交事務,對於依賴事務也只有在異步調用的時候會使用到,基於TransactionScope的事務 編程方式才是我們推薦的。
正如其名稱所表現的一樣,TransactionScope就是為一組事務型操作創建一個執行范圍,而這個范圍 始於TransactionScope創建之時,結束於TransactionScope被回收(調用Dispose方法)。在對 TransactionScope進行深入介紹之前,照例先來看看它的定義:
1: public sealed class TransactionScope : IDisposable
2: {
3: public TransactionScope();
4: public TransactionScope(Transaction transactionToUse);
5: public TransactionScope(TransactionScopeOption scopeOption);
6: public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout);
7: public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout);
8: public TransactionScope (TransactionScopeOption scopeOption, TransactionOptions transactionOptions);
9: public TransactionScope(Transaction transactionToUse, TimeSpan scopeTimeout, EnterpriseServicesInteropOption interopOption);
10: public TransactionScope (TransactionScopeOption scopeOption, TransactionOptions transactionOptions, EnterpriseServicesInteropOption interopOption);
11:
12: public void Complete();
13: public void Dispose();
14: }
我們可以看到TransactionScope實現了IDisposable接口,除了Dispose方法之外,僅僅具有一個唯一 的方法:Complete。但是TransactionScope卻有一組豐富的構造函數。我們先來看看這些構造函數相應 的參數如何影響TransactionScope對事務控制的行為。
1、TransactionScopeOption
實際上前面一節中提供的InvokeInTransaction方法基本上體現了TransactionScope的內部實現。也 就是說,TransactionScope也是通過創建可提交事務或者依賴事務,並將其作為事務范圍內的環境事務 ,從而將范圍的所有操作納入到一個事務之中。
通過在構造函數中指定TransactionScopeOption類型的scopeOption參數,控制TransactionScope當 環境事務存在的時候應該采取怎樣的方式執行事務范圍內的操作。具有來講,具有三種不同的方式:
* 如果已經存在環境事務,則使用該環境事務。否則,在進入范圍之前創建新的事務;
* 總是為該范圍創建新事務;
* 環境事務上下文在創建范圍時被取消。范圍中的所有操作都在無環境事務上下文的情況下完成。
TransactionScopeOption是一個枚舉,三個枚舉值Required、RequiresNew和Suppress依次對應上面 的三種行為。
1: public enum TransactionScopeOption
2: {
3: Required,
4: RequiresNew,
5: Suppress
6: }
對於Required選項,如果當前存在環境事務TransactionScope會創建環境事務的依賴事務,負責創建 可提交事務,然後將創建的環境事務或者可提交事務作為事務范圍的環境事務。如對於RequiresNew選項 ,TransactionScope總是會創建可提交事務並將其作為事務范圍的環境事務,意味著控制事務范圍內操 作的事務也當前的環境事務已經沒有任何關系。如果Suppress選項,TransactionScope會將事務范圍內 的環境事務設為空,意味著事務范圍內的操作並不受事務的控制。
Required是默認選項,意味著事務范圍內的事務將會作為當前環境事務的一部分。如果你不希望某個 操作被納入當前的環境事務,但是相應的操作也需要事務的控制以確保所操作數據的一致性。比如,當 業務邏輯失敗導致異常拋出,需要對相應的錯誤信息進行日志記錄。對於日記的操作就可以放入基於 RequiresNew選項創建TransactionScope中。對於一些不重要的操作(操作的錯誤可被忽略),並且不需 要通過事務來控制的操作,比如發送一些不太重要的通知,就可以采用Suppress選項。
2、TransactionOptions和EnterpriseServicesInteropOption
TransactionOptions在前面已經提及,用於控制事務的超時時限和隔離級別。對於超時時限,你也可 以選擇 TransactionScope相應能夠的構造函數以TimeSpan的形式指定。而對於事務的隔離級別,需要著 重強調一點:當選擇 TransactionScopeOption.Required選項時,TransactionScope指定的隔離級別必 須與環境事務(如果有)相匹配。
比如下面的例子中,我定義兩個嵌套的TransactionScope,外部的TransactionScope采用默認的隔離 級別,內部在采用ReadCommitted隔離級別,當執行這段代碼的時候,會拋出如圖1所示的 ArgumentException異常。
1: using (TransactionScope outerScope = new TransactionScope())
2: {
3: TransactionOptions transactionOptions = new TransactionOptions() { IsolationLevel = IsolationLevel.ReadCommitted };
4: using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
5: {
6: //事務型操作
7: innerScope.Complete();
8: }
9: //事務型操作
10: outerScope.Complete();
11: }
圖1 隔離級別不一致導致的異常
實際上在System.Transactions事務機制被引入之前,像Enterprise Service主要依賴於基於COM+的 分布式事務。TransactionScope通過 EnterpriseServicesInteropOption控制System.Transactions事務 如何與COM+的分布式事務進行互操作。具有來講具有如下三種互操作選項,分別和 EnterpriseServicesInteropOption三個枚舉值相對應:
* None:Transaction 和 Current 之間不同步;
* Automatic:搜索現有的 COM+ 上下文並與之同步(如該上下文存在);
* Full:System.EnterpriseServices 上下文(可通過調用 ContextUtil 類的靜態方法 Transaction 來檢索)和 System.Transactions 環境事務(可通過調用 Transaction 類的靜態方法 Current 來檢索)始終保持同步。這將引入性能損失,因為可能需要創建新的 System.EnterpriseServices 上下文。
1: public enum EnterpriseServicesInteropOption
2: {
3: None,
4: Automatic,
5: Full
6: }
3、事務提交和回滾
對於事務范圍中的事務,無論是事務的提交(對於可提交事務)、完成(依賴事務)和回滾都是在 Dispose方法中執行的。 TransactionScope中定一個個私有的布爾類型字段(complete)表示事務是否 正常結束。該成員的默認值為False,當調用 TransactionScope的Complete方法的時候會將此字段設置 成True。當Dispose執行的時候,如果該字段的值為False,會調用事務的Rollback方法對該事務實施回 滾;否則會調用Commit方法(對於可提交事務)對事務進行提交或者調用Complete方法(依賴事務)通 知被依賴的事務本地事務已經正常完成。
除了執行事務的提交、完成或者回滾之外,TransactionScope的Dispose方法還負責將環境事務回復 到事務范圍開始之前的狀態。在調用Complete和Dispose之前,環境事務處於不可用的狀態,如果此時試 圖獲取環境事務,會拋出異常。比如在下面的代碼中,在事務范圍內部調用 Complete方法後,通過 Transaction的Current靜態屬性獲取當前環境事務,會拋出圖2所示的InvalidOpertionException異常。
1: using (TransactionScope transactionScope = new TransactionScope())
2: {
3: //其他事務操作
4: transactionScope.Complete();
5: Transaction ambientTransaction = Transaction.Current;
6: }
圖2 在TransactionScope完成之後獲取環境事務導致的異常
出處:http://artech.cnblogs.com