C#綜合揭秘——細說事務(wù)
引言
其實事務(wù)在數(shù)據(jù)層、服務(wù)層、業(yè)務(wù)邏輯層多處地方都會使用到,在本篇文章將會為大家一一細說。
其中前面四節(jié)是事務(wù)的基礎(chǔ),后面的三節(jié)是事務(wù)的重點,對事務(wù)有基礎(chǔ)的朋友可以跳過前面四節(jié)。
文章有錯漏的地方歡迎各位點評。
目錄
一、事務(wù)的定義
所謂事務(wù),它是一個操作集合,這些操作要么都執(zhí)行,要么都不執(zhí)行,它是一個不可分割的工作單位。典型的例子就像從網(wǎng)上銀行系統(tǒng)的帳戶A轉(zhuǎn)帳到帳戶B,它經(jīng)過兩個階段:1.從帳戶A取出款項。2.把款項放入帳戶B中。這兩個過程要么同時成功,要么同時失敗,這一系列的操作就被稱為事務(wù)性(Transactional)操作。
在一個事務(wù)性操作的環(huán)境下,操作有著以下的4種特性,被稱為ACID特性
| 原子性(Atomicity) | 當事務(wù)結(jié)束,它對所有資源狀態(tài)的改變都被視為一個操作,這些操作要不同時成功,要不同時失敗 |
| 一致性(Consistency) | 操作完成后,所有數(shù)據(jù)必須符合業(yè)務(wù)規(guī)則,否則事務(wù)必須中止 |
| 隔離性(Isolation) | 事務(wù)以相互隔離的方式執(zhí)行,事務(wù)以外的實體無法知道事務(wù)過程中的中間狀態(tài) |
| 持久性(Durable) | 事務(wù)提交后,數(shù)據(jù)必須以一種持久性方式存儲起來 |
二、事務(wù)管理器
在軟件系統(tǒng)當中可以看到無論在數(shù)據(jù)庫、Web服務(wù)、WCF、文件系統(tǒng)都存在著數(shù)據(jù)參與到事務(wù)運作當中,我們把管理這些數(shù)據(jù)的工具稱為資源管理器RM(Resources Manager)。而事務(wù)管理器TM(Transaction Manager)就是協(xié)調(diào)多個資源管理器的工作,保證數(shù)據(jù)完整性的工具。

由上圖可以看到事務(wù)的管理流程,系統(tǒng)通知事務(wù)管理器TM來啟動事務(wù),事務(wù)管理器TM控制向多個資源管理器RM并協(xié)調(diào)RM之間的事務(wù)操作。圖中存在兩個持久化RM,分別管理數(shù)據(jù)庫和文件系統(tǒng),這些事務(wù)操作要不同時成功,要不同時失敗。
事務(wù)管理器一般分為三類:輕量級事務(wù)管理器(LTM)、核心事務(wù)管理器(KTM)、分布式事務(wù)協(xié)調(diào)器(DTC)
1. 輕量級事務(wù)管理器 (LTM)
它是包括在System.Transactions 命名空間內(nèi)的一個事務(wù)管理框架,它只能管理單個應(yīng)用程序域內(nèi)的事務(wù)。LTM 可以管理多個易變的RM,但只能管理一個持久化RM。若事務(wù)試圖加入第二個持久化RM,那輕量級事務(wù)管理器LTM將提升級別。LTM是性能最高的事務(wù)管理器,在可選擇的情況下應(yīng)該盡可能地使用 LTM 事務(wù)管理器。
這里易變RM是指它參與會引發(fā) “未確定狀態(tài)” 的2PC事務(wù)時候,不需要恢復服務(wù),更多時候,易變RM的數(shù)據(jù)只存儲在內(nèi)存當中。
而持久化RM是指它參與會引發(fā) “未確定狀態(tài)” 的2PC事務(wù)時候,它需要恢復服務(wù),持久化RM管理的數(shù)據(jù)是在于硬盤當中。所以,參與2PC事務(wù)的的持久RM必須有新舊兩個版本,如果事務(wù)引發(fā) “未確定狀態(tài)” 的時候,那么它就會聯(lián)系持久化RM,恢復到其中一個版本。
2PC 是2 Phase Commit的縮寫,代表事務(wù)的2階段提交驗證算法:在數(shù)據(jù)提交時,第一階段:應(yīng)用程序記錄每個數(shù)據(jù)源并執(zhí)行更新請求,TM通知每個RM來執(zhí)行分布式事 務(wù),然后每個RM都對數(shù)據(jù)執(zhí)行本地的事務(wù),在事務(wù)將提交前,TM會與各個RM進行信息交換,以獲知更新是否成功。第二階段,如果其中任何一個RM表示更新 失敗,TM就會通知所有的RM事務(wù)操作失敗,實現(xiàn)數(shù)據(jù)回滾。如果所有RM的操作都成功,那么整個TM事務(wù)就宣告成功。
2. 核心事務(wù)管理器 (KTM)
KTM是用于Windows Vista和Windows Server 2008 系統(tǒng)中的輕量級事務(wù)管理器,與LTM相像,它可以管理多個易變的RM,但只能管理一個持久化RM。
3. 分布式事務(wù)協(xié)調(diào)器(DTC)
分布式事務(wù)協(xié)調(diào)器DTC(Distributed Transaction Coordinator)能管理多個持久化RM中的事務(wù),事務(wù)可以跨越應(yīng)用程序域、進程、硬件、域等所有的邊界。在Windows Server 2008當中,DTC支持OleDB、XA、WS-AtomicTransaction、WSCoordination、WS-BusinessActivity等多個協(xié)議。由于分布式事務(wù)需要在多個參與方之間實現(xiàn)多次通訊,所以是一種巨大的開銷,因此,在可以使用LTM和KTM的時候,應(yīng)該盡量避免使用DTC。在上面圖片中的事務(wù)同時啟動了兩個RM分別處理數(shù)據(jù)庫數(shù)據(jù)與文件數(shù)據(jù),當中啟動的就是DTC分布式事務(wù)。
4.事務(wù)類 System.Transactioins.Transaction
Transaction是由Framework 2.0 就開始引入,用于顯示管理事務(wù)的一個類。通過Transaction可以直接管理事務(wù)的中止、釋放,也可以獲取、克隆當前的環(huán)境事務(wù)類。
- Transaction的公用屬性
其中Transaction.Current 比較常用,它可以指向一個當前運行環(huán)境中的事務(wù),如果環(huán)境事務(wù)不存在,系統(tǒng)將返回一個null
Transaction transaction=Transaction.Current;
| 屬性 | 說明 |
|---|---|
| Current | 獲取或設(shè)置環(huán)境事務(wù)。 |
| IsolationLevel | 獲取事務(wù)的隔離級別。 |
| TransactionInformation | 檢索有關(guān)某個事務(wù)的附加信息。 |
- Transaction的常用公用方法
其中Rollback、Dispose方法可以控制事務(wù)中止、釋放,而Clone、DependentClone方法在多線程操作中經(jīng)常用到,在 “異步事務(wù)” 一節(jié)中將詳細說明
| 方法 | 說明 |
|---|---|
| Rollback | 中止事務(wù)、回滾。 |
| Dispose | 釋放事務(wù)對象。 |
| Clone | 創(chuàng)建事務(wù)克隆 |
| DependentClone | 創(chuàng)建事務(wù)的依賴克隆。 |
- Transaction的事件
在事務(wù)完成后,會觸發(fā)TransactionCompleted事件,開發(fā)人員可以在此事件的過程監(jiān)測其狀態(tài)
| 事件 | 說明 |
|---|---|
| TransactionCompleted | 在事務(wù)完成后執(zhí)行 |
5. 事務(wù)狀態(tài) TransactionInformation
上面講解過事務(wù)分為本地事務(wù)與分布式事務(wù),而Transaction類的TransactionInformation是事務(wù)狀態(tài)的記錄,它可以跟蹤事務(wù)動作,分辨事務(wù)現(xiàn)處的狀態(tài),記錄本地事務(wù)與分布式事務(wù)的Guid。
TransactionInformation有兩個重要成員
1 public class TransactionInformation
2 {
3 //返回分布式事務(wù)標識符
4 public Guid DistributedIdentifier
5 {get;}
6
7 //返回本地事務(wù)標識符
8 public string LocalIdentifier
9 {get;}
10 }
LocalIndentifier是本地事務(wù)的標識符,它可以獲取本地事務(wù)管理器(LTM)的ID,并且注意只要事務(wù)存在,它的值就永遠不會是null。它包含兩個部分,一個是LTM的Guid,它是應(yīng)用程序中的唯一值,代表了現(xiàn)存應(yīng)用程序域分配的LTM。另一部分是一個可變量,代表了當時該應(yīng)用程序域中的事務(wù)數(shù)量。
例如:3427dec9-4abc-34cc-9edf-30ad835c33k3:3
其中3427dec9-4abc-34cc-9edf-30ad835c33k3是此本地事務(wù)管理器的Guid,在事務(wù)啟動后,此值都是不變的,而 “3” 代表此刻該應(yīng)用程序域中存在 “3” 個本地事務(wù)。
DistributedIndentifier是分布式事務(wù)的標識符,在普通情況下DistributedIndentifier的值都為Guid.Empty。但當LTM或KTM事務(wù)被提升到分布式事務(wù)時,DistributedIndentifier就會產(chǎn)生。最重的是,在同一個分布式事務(wù)管理器當中,即使事務(wù)跨越服務(wù)邊界,分布式ID都是一致的。DistributedIndentifier是分布式事務(wù)的唯一標識符,它的使用方法在后面 “事務(wù)的傳播” 一節(jié)將詳細介紹。
在TransactionManager類中,還提供了一個事件DistributedTransactionStarted專門用于測試分布式事務(wù)的變化。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 using (TransactionScope scope = new TransactionScope())
6 {
7 TransactionManager.DistributedTransactionStarted += OnDistributedTransactionStarted;
8 ............
9 scope.Complete();
10 }
11 Console.ReadKey();
12 }
13
14 //當執(zhí)行分布式事務(wù)是就會啟動此方法顯示事務(wù)信息
15 static void OnDistributedTransactionStarted(object sender, TransactionEventArgs args)
16 {
17 Transaction transaction = args.Transaction;
18 Console.WriteLine("Distributed Transaction Started!\n DistributedIndentifier:"
19 +transaction.TransactionInformation.DistributedIdentifier);
20 }
21 }
基礎(chǔ)知識就先講到這里,下面開始介紹一下事務(wù)的具體用法。
三、在ADO.NET中實現(xiàn)事務(wù)
1. ADO.NET事務(wù)的主要成員
需要使用事務(wù)管理的情況很多,在數(shù)據(jù)層使用得特別廣泛,幾乎每一個系統(tǒng)的數(shù)據(jù)層都會實現(xiàn)事務(wù)。數(shù)據(jù)層的事務(wù)都是繼承自DBTransaction,派生自IDbTransaction的。下面介紹一下IDbTransaction的基本成員:
1 public interface IDbTransaction:IDisposable
2 {
3 IDbConnection Connection {get;} //返回Connection對象
4 IsolationLevel IsolationLevel{get;}
5 void Commit(); //數(shù)據(jù)提交,把所有改變數(shù)據(jù)保存到持久化數(shù)據(jù)庫
6 void Rollback(); //數(shù)據(jù)回滾,把所有數(shù)據(jù)恢復原值
7 }
其中Connection屬性是返回初始化此事務(wù)時所引用的連接對象的。Commit()方法應(yīng)該在完成所有數(shù)據(jù)操作后才調(diào)用,調(diào)用該方法后,已經(jīng)改變的數(shù)據(jù)將會保存到持久化數(shù)據(jù)庫當中。而Rollback()是出現(xiàn)錯誤時調(diào)用的,調(diào)用后數(shù)據(jù)將返回初始值。IsolationLevel是指定遇到其它并行事務(wù)時的處理方式。
ADO.NET當中有多個子類都繼續(xù)自DBTransaction,其中SqlTransaction是比較常用的,SqlTransaction中還定義了一個Save()方法,這個方法允許開發(fā)人員把失敗的事務(wù)回滾到上一個保存點而不回滾整個事務(wù)。而在DataContext類里面,Transaction屬性會返回DBTransaction對象。

2.開發(fā)實例
在傳統(tǒng)的ADO.NET中使用事務(wù),方法如下:
1 private static void Execute(string connectionString)
2 {
3 using (SqlConnection connection = new SqlConnection(connectionString))
4 {
5 connection.Open();
6
7 SqlCommand command = connection.CreateCommand();
8 SqlTransaction transaction;
9
10 //啟動事務(wù)
11 transaction = connection.BeginTransaction("SampleTransaction");
12
13 //設(shè)定SqlCommand的事務(wù)和連接對象
14 command.Connection = connection;
15 command.Transaction = transaction;
16
17 try
18 {
19 command.CommandText ="Insert into ......";
20 command.ExecuteNonQuery();
21
22 // 完成提交
23 transaction.Commit();
24 ......
25 }
26 catch (Exception ex)
27 {
28 //數(shù)據(jù)回滾
29 transaction.Rollback();
30 .....
31 }
32 }
33 }
在DataContext中使用事務(wù),方法極其相似,不同的是SqlCommand中事務(wù)為SqlTransaction,在DataContext中事務(wù)為DbTransaction
1 using(MyDataContext context=new MyDataContext())
2 {
3 try
4 {
5 context.Connection.Open();
6 context.Transaction=context.Connection.BeginTransaction();
7 //更新數(shù)據(jù)
8 .........
9 context.SubmitChanges();
10 //事務(wù)提交
11 context.Transaction.Commit();
12 }
13 catch(Excetion ex)
14 {
15 //數(shù)據(jù)回滾
16 context.Transaction.Rollback();
17 //錯誤處理
18 .........
19 }
20 }
四、隱式事務(wù) TransactionScope
1. TransactionScope的概念
TransactionScope存在于System.Transactions 命名空間中, 它是從Framework 2.0開始引入的一個事務(wù)管理類,它也是微軟推薦使用的一個事務(wù)管理類。在TransactionScope的構(gòu)造函數(shù)中會自動創(chuàng)建了一個新的LTM(輕量級事務(wù)管理器),并通過Transaction.Current 隱式把它設(shè)置為環(huán)境事務(wù)。在使用隱式事務(wù)時,事務(wù)完成前程序應(yīng)該調(diào)用TransactionScope的Complete()方法,把事務(wù)提交,最后利用Dispose()釋放事務(wù)對象。若執(zhí)行期間出現(xiàn)錯誤,事務(wù)將自動回滾。
1 public class TransactionScope:IDisposable
2 {
3 //多個構(gòu)造函數(shù)
4 public TransactionScope();
5 public TransactionScope(Transaction)
6 public TransactionScope(TransactionScopeOption)
7 ......
8 public void Complete(); //提交事務(wù)
9 public void Dispose(); //釋放事務(wù)對象
10 }
11
12 //調(diào)用方式
13 using(TransactionScope scope=new TransactionScope())
14 {
15 //執(zhí)行事務(wù)型工作
16 ............
17 scope.Complete();
18 }
2. TransactionScope的構(gòu)造函數(shù) TransactionScope(transactionScopeOption)
TransactionScopeOption 是枚舉的一個實例,它主要用于TransactionScope的構(gòu)造函數(shù)內(nèi),定義事務(wù)生成的狀態(tài)要求。
在MSDN里面可以找到它的定義:
http://msdn.microsoft.com/zh-cn/library/system.transactions.transactionscopeoption.aspx
| 成員名稱 | 說明 |
|---|---|
| Required | 該范圍需要一個事務(wù)。 如果已經(jīng)存在環(huán)境事務(wù),則使用該環(huán)境事務(wù)。 否則,在進入范圍之前創(chuàng)建新的事務(wù)。 這是默認值。 |
| RequiresNew | 總是為該范圍創(chuàng)建新事務(wù)。 |
| Suppress | 環(huán)境事務(wù)上下文在創(chuàng)建范圍時被取消。 范圍中的所有操作都在無環(huán)境事務(wù)上下文的情況下完成。 |
這里Suppress有點特別,當使用Suppress范圍內(nèi),所有的操作都將在無事務(wù)的上下文中執(zhí)行,即當中的程序不再受到事務(wù)的保護,這大多數(shù)在嵌套式事務(wù)中使用。
1 void DoWork()
2 {
3 using(TransactionScope scope=new TransactionScope())
4 {
5 //在事務(wù)環(huán)境中執(zhí)行操作
6 ......
7 NoTransaction();
8 scope.Complete();
9 }
10 }
11
12 void NoTransaction()
13 {
14 //在無事務(wù)環(huán)境中執(zhí)行操作
15 using(TransactionScope scope=new TransactionScope(TransactionScopeOption.Suppress))
16 {
17 ......
18 }
19 }
3. 應(yīng)用實例,在Entity Framework中使用TransactionScope
在 ObjectContext.SaveChanges 方法中已經(jīng)包含了事務(wù)的保護,自從微軟開源了Entity Framework 后,在下才在CodePlex 了解到其實現(xiàn)方式。一般在單體個實體的插入、更新、刪除等簡單操作中,不需要額外加載事務(wù)。除非在關(guān)聯(lián)表操作,多次使用ObjectContext.SaveChanges方法時,此時則需要使用事務(wù)保存數(shù)據(jù)操作的一致性。因為在SaveChanges中已經(jīng)包含了事務(wù),此時所實現(xiàn)的事務(wù)操作已經(jīng)第六節(jié)所說到的嵌套式事務(wù)。
1 public int UpdateOrder(Order order) 2 { 3 using (BusinessEntities context = new BusinessEntities()) 4 { 5 EntityKey entityKey = new 6 EntityKey("BasicArchitectureEntities.Order", "Id", order.Id); 7 var objResult = context.GetObjectByKey(entityKey); 8 9 using (TransactionScope scope = new TransactionScope()) 10 { 11 if (objResult != null) 12 context.ApplyCurrentValues<Order>("Order", order); 13 foreach (OrderItem item in order.OrderItem) 14 UpdateOrderItem(item); 15 return context.SaveChanges(); 16 } 17 } 18 } 19 20 public int UpdateOrderItem(OrderItem item) 21 { 22 using (BusinessEntities context = new BusinessEntities()) 23 { 24 EntityKey entityKey = new 25 EntityKey("BasicArchitectureEntities.OrderItem", "Id", item.Id); 26 var objResult = context.GetObjectByKey(entityKey); 27 28 //更新實體屬性 29 if (objResult != null) 30 context.ApplyCurrentValues<OrderItem>("OrderItem", item); 31 return context.SaveChanges(); 32 } 33 }
五、在WCF中實現(xiàn)事務(wù)
1. WCF服務(wù)中事務(wù)的邊界
把WCF的事務(wù)邊界獨立成一節(jié),是想大家注意這一點,WCF服務(wù)中,事務(wù)是以方法為邊界的,每個WCF服務(wù)的方法可以有獨立事務(wù)的執(zhí)行模式。而事務(wù)可以在多個服務(wù)中傳播,也可以在服務(wù)端與客戶端之間傳播,介時事務(wù)管理器的級數(shù)將會晉升。
2.簡單的事務(wù)使用方式
TransactionScopeRequired與TransactionAutoComplete是WCF事務(wù)的基本元素。
當TransactionScopeRequired等于true時,代表在此WCF服務(wù)的方法中啟動事務(wù)。反之,當此值為false時代表此方法不執(zhí)行事務(wù)。
當TransactionAutoComplete等于true時,代表該方法使用隱式事務(wù),這也是微軟推薦使用的方法。即當該方法在運行過程中沒有拋出Exception,操作就默認為完成,事務(wù)將自動提交。如果期間出現(xiàn)任何異常,事務(wù)就會自動回滾。如果TransactionAutoComplete等于false時,該方法即為顯式事務(wù),即需要在方法完成時利用OperationContext.Current.SetTransactionComplete () 顯式提交事務(wù)。
1 [ServiceContract(SessionMode=SessionMode.Required)]
2 public interface IService
3 {
4 [OperationContract]
5 void Method1();
6
7 [OperationContract]
8 void Method2();
9 }
10
11 public class Service : IService
12 {
13 //隱式事務(wù)
14 [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
15 public void Method1()
16 {
17 ...........
18 }
19
20 //顯式事務(wù)
21 [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=false)]
22 public void Method2()
23 {
24 ...........
25 OperationContext.Current.SetTransactionComplete();
26 }
27 }
3. 事務(wù)的傳播
在同一個應(yīng)用程序域中,事務(wù)默認能相互傳播,在上面的例子當中,當方法Method1()直接調(diào)用Mehtod2()的時候,事務(wù)也能夠成功流轉(zhuǎn)。
事務(wù)也能夠在服務(wù)端與客戶端之間傳播,還能跨越服務(wù)邊界,在多個系統(tǒng)當中流轉(zhuǎn),在WCF里把服務(wù)中的事務(wù)傳播稱作事務(wù)流(Transaction Flow)。如果事務(wù)流需要在服務(wù)端和客戶端成功傳播或使用分布式事務(wù),必須具備以下條件:
- 綁定必須支持事務(wù),在WCF內(nèi)并非所有綁定都支持事務(wù),像常用的BasicHttpBinding就不支持事務(wù)的傳播。只有以下幾個綁定才能支持事務(wù)流的運轉(zhuǎn):NetTcpBinding、WSHttpBinding、WSDualHttpBinding、WSFederationHttpBinding、NetNamedPipeBinding。
- 服務(wù)方法必須設(shè)置好TransactionScopeRequired與TransactionAutoComplete兩個事務(wù)的基本元素,要成功啟動事務(wù),這是基礎(chǔ)條件。
- 把TransactionFlow設(shè)置為true,這代表啟動事務(wù)流,允許在SOAP頭部放置事務(wù)的信息。一般情況下TransactionFlow的默認值為false ,這表示事務(wù)只能在服務(wù)器的同一應(yīng)用程序域內(nèi)流轉(zhuǎn),而不能實現(xiàn)服務(wù)端與客戶端之間的傳播。
- 把服務(wù)契約的TransactionFlowOption設(shè)置為Allowed,這代表允許客戶端的事務(wù)傳播到服務(wù)端。
- 客戶端必須啟動一個事務(wù),在最后使用TransactionScope.Complete ( ) 提交事務(wù)。
TransactionFlowOption有三個選項:
一為NotAllowed,這代表了禁止客戶端傳播事務(wù)流到服務(wù)端,即使客戶端啟動了事務(wù),該事務(wù)也會被忽略;
二為Allowed,這代表允許客戶端的事務(wù)傳播到服務(wù)端,但服務(wù)器端不一定會引用到此事務(wù);
三為Mandatory,這代表服務(wù)端與客戶端必須同時啟動事務(wù)流,否則就會拋出InvalidOperationException異常。
下面舉幾個例子來講解一下事務(wù)流的使用方式。
3.1 在服務(wù)端與客戶端之間傳播事務(wù)
這是事務(wù)流的基本使用方式,首先在服務(wù)端使用wsHttpBinding綁定建立一個服務(wù)契約,在方法中利用TransactionInformation對象檢測一下事務(wù)的狀態(tài)。然后設(shè)置好TransactionScopeRequired與TransactionAutoComplete屬性來啟動事務(wù),在*.config中把TransactionFlow設(shè)置為true。再把服務(wù)的TransactionFlowOption設(shè)置為Allowed,最后在客戶端通過TransactionScope.Complete()的方法提交事務(wù)。
服務(wù)端:
1 namespace Example
2 {
3 [ServiceContract]
4 public interface IExampleService
5 {
6 [OperationContract]
7 void Method1();
8 }
9
10 public class ExampleService : IExampleService
11 {
12 //使用隱式事務(wù),并把TransactionFlowOption設(shè)置為Allowed打開事務(wù)流
13 [OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)]
14 [TransactionFlow(TransactionFlowOption.Allowed)]
15 public void Method1()
16 {
17 //通過TransactionInformation檢測事務(wù)狀態(tài)
18 Transaction transaction = Transaction.Current;
19 string info=string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
20 transaction.TransactionInformation.DistributedIdentifier,
21 transaction.TransactionInformation.LocalIdentifier);
22 Console.WriteLine("Method1: \n"+info);
23 }
24
25 static void Main(string[] args)
26 {
27 //啟動服務(wù)
28 ServiceHost exampleHost = new ServiceHost(typeof(Example.ExampleService));
29
30 exampleHost.Open();
31
32 Console.WriteLine("service start");
33 Console.ReadKey();
34 Console.WriteLine("service end");
35
36 exampleHost.Close();
37 }
38 }
39 }
40
41 <configuration>
42 <system.serviceModel>
43 <bindings>
44 <wsHttpBinding>
45 <!--啟動事務(wù)流-->
46 <binding name="defaultWSHttpBinding" transactionFlow="true" />
47 </wsHttpBinding>
48 </bindings>
49 <behaviors>
50 <serviceBehaviors>
51 <behavior name="">
52 <serviceMetadata httpGetEnabled="true" />
53 <serviceDebug includeExceptionDetailInFaults="false" />
54 </behavior>
55 </serviceBehaviors>
56 </behaviors>
57 <services>
58 <service name="Example.ExampleService">
59 <!--使用支持事務(wù)流的wsHttpBinding綁定-->
60 <endpoint address="" binding="wsHttpBinding" bindingConfiguration="defaultWSHttpBinding"
61 contract="Example.IExampleService">
62 <identity>
63 <dns value="localhost" />
64 </identity>
65 </endpoint>
66 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
67 <host>
68 <baseAddresses>
69 <add baseAddress="http://localhost:7200/Example/ExampleService/" />
70 </baseAddresses>
71 </host>
72 </service>
73 </services>
74 </system.serviceModel>
75 </configuration>
客戶端:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
6 {
7 ShowTransactionMessage("start");
8
9 ExampleServiceReference.ExampleServiceClient exampleService1 = new
10 ExampleServiceReference.ExampleServiceClient();
11 exampleService1.Method1();
12 ShowTransactionMessage("exampleService started");
13 exampleService1.Close();
14 //事務(wù)提交
15 scope.Complete();
16 }
17 Console.ReadKey();
18 }
19
20 //檢查事務(wù)的狀態(tài)
21 static void ShowTransactionMessage(string data)
22 {
23 if (Transaction.Current != null)
24 {
25 Transaction transaction = Transaction.Current;
26 string info = string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
27 transaction.TransactionInformation.DistributedIdentifier,
28 transaction.TransactionInformation.LocalIdentifier);
29 Console.WriteLine(data+" \n" + info);
30 }
31 }
32 }
圖中上面代表服務(wù)端,下面代表客戶端。可以看到,在客戶端剛啟動事務(wù)時,事務(wù)只一般的LTM輕量級事務(wù),DistributedIndentifier為空值。當調(diào)用ExampleService服務(wù)后,事務(wù)的級別便提升為分布式事務(wù)。而服務(wù)端與客戶端的事務(wù)正是通過DistributedIndentifier為標識的。

3.2使用分布式事務(wù)協(xié)調(diào)多個服務(wù)端的操作
在分布式系統(tǒng)當中,單個客戶端可能引用多個服務(wù),分布式事務(wù)能協(xié)調(diào)多方的操作。多個系統(tǒng)中的操作要不同時成功,要不同時失敗。下面的例子中,客戶端同時引用了ExampleService服務(wù)和ExtensionService服務(wù),并啟動了分布式事務(wù)。而在客戶端與兩個服務(wù)端的事務(wù)都是通過DistributedIndentifier 作為事務(wù)的標識的。
服務(wù)端:
1 //*******************************ExampleService*************************************************************************//
2 namespace Example
3 {
4 [ServiceContract]
5 public interface IExampleService
6 {
7 [OperationContract]
8 void Method1();
9 }
10
11 public class ExampleService : IExampleService
12 {
13 //使用隱式事務(wù),并把TransactionFlowOption設(shè)置為Allowed
14 [OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)]
15 [TransactionFlow(TransactionFlowOption.Allowed)]
16 public void Method1()
17 {
18 //通過TransactionInformation檢測事務(wù)狀態(tài)
19 Transaction transaction = Transaction.Current;
20 string info=string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
21 transaction.TransactionInformation.DistributedIdentifier,
22 transaction.TransactionInformation.LocalIdentifier);
23 Console.WriteLine("Method1: \n"+info);
24 }
25
26 static void Main(string[] args)
27 {
28 //啟動服務(wù)
29 ServiceHost exampleHost = new ServiceHost(typeof(Example.ExampleService));
30
31 exampleHost.Open();
32
33 Console.WriteLine("service start");
34 Console.ReadKey();
35 Console.WriteLine("service end");
36
37 exampleHost.Close();
38 }
39 }
40 }
41
42 <configuration>
43 <system.serviceModel>
44 <bindings>
45 <wsHttpBinding>
46 <!--啟動事務(wù)流-->
47 <binding name="defaultWSHttpBinding" transactionFlow="true" />
48 </wsHttpBinding>
49 </bindings>
50 <behaviors>
51 <serviceBehaviors>
52 <behavior name="">
53 <serviceMetadata httpGetEnabled="true" />
54 <serviceDebug includeExceptionDetailInFaults="false" />
55 </behavior>
56 </serviceBehaviors>
57 </behaviors>
58 <services>
59 <service name="Example.ExampleService">
60 <!--使用支持事務(wù)流的wsHttpBinding綁定-->
61 <endpoint address="" binding="wsHttpBinding" contract="Example.IExampleService"
62 bindingConfiguration="defaultWSHttpBinding" >
63 <identity>
64 <dns value="localhost" />
65 </identity>
66 </endpoint>
67 <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
68 <host>
69 <baseAddresses>
70 <add baseAddress="http://localhost:7200/Example/ExampleService/" />
71 </baseAddresses>
72 </host>
73 </service>
74 </services>
75 </system.serviceModel>
76 </configuration>
77
78 //*************************************Extension**************************************************************//
79 namespace Extension
80 {
81 [ServiceContract]
82 public interface IExtensionService
83 {
84 [OperationContract]
85 void DoWork();
86 }
87
88 public class ExtensionService : IExtensionService
89 {
90 [OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)]
91 [TransactionFlow(TransactionFlowOption.Allowed)]
92 public void DoWork()
93 {
94 Transaction transaction = Transaction.Current;
95 string info = string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
96 transaction.TransactionInformation.DistributedIdentifier,
97 transaction.TransactionInformation.LocalIdentifier);
98 Console.WriteLine("DoWork: \n" + info);
99 }
100
101 static void Main(string[] args)
102 {
103 Console.WriteLine("extension service start");
104 ServiceHost host = new ServiceHost(typeof(ExtensionService));
105 host.Open();
106 Console.ReadKey();
107 host.Close();
108 }
109 }
110 }
111 <configuration>
112 <!--略-->
113 ...................
114 </configuration>
客戶端
1 namespace Test
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
8 {
9 ShowTransactionMessage("start");
10
11 ExampleServiceReference.ExampleServiceClient exampleService1 = new
12 ExampleServiceReference.ExampleServiceClient();
13 exampleService1.Method1();
14 ShowTransactionMessage("exampleService started");
15 ExtensionServiceReference.ExtensionServiceClient extensionService = new
16 ExtensionServiceReference.ExtensionServiceClient();
17 extensionService.DoWork();
18 ShowTransactionMessage("extensionService started");
19
20 exampleService1.Close();
21 extensionService.Close();
22
23 scope.Complete();
24 }
25
26 Console.ReadKey();
27 }
28
29 //檢查事務(wù)的狀態(tài)
30 static void ShowTransactionMessage(string data)
31 {
32 if (Transaction.Current != null)
33 {
34 Transaction transaction = Transaction.Current;
35 string info = string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
36 transaction.TransactionInformation.DistributedIdentifier,
37 transaction.TransactionInformation.LocalIdentifier);
38 Console.WriteLine(data+" \n" + info);
39 }
40
41 }
42 }
43 }
從測試結(jié)果可以看到在多個不同的服務(wù)端與客戶端之間,都是通過DistributedIndentifier分布式事務(wù)ID來進行信息溝通的。一旦任何一出現(xiàn)問題,事務(wù)都會共同回滾,這對分布式開發(fā)是十分重要的。

值得注意的一點,事務(wù)必須由客戶端提交,當客戶端調(diào)用無事務(wù)狀態(tài)時,兩個服務(wù)端的事務(wù)則無法進行辨認,即其中一方出錯,事務(wù)出現(xiàn)回滾,另一方也無法感知,這樣容易引起邏輯性錯誤。試著把客戶端代碼改為 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress)){...},再運作一下,可以看到以下結(jié)果。事務(wù)都是由兩個服務(wù)端分別管理,系統(tǒng)并無啟動分布式事務(wù)。

應(yīng)該注意:分布式事務(wù)會耗費較大的資源,在沒必要的情況下,應(yīng)該盡量使用LTM級量級事務(wù)管理器,而避免使用DTC分布式事務(wù)管理。
4.事務(wù)的的隔離性
事務(wù)的隔離性是通過TransactionIsolationLevel來定義的,它存在以下幾個級別:
| Unspecified | |
| ReadUncommitted | 在讀取數(shù)據(jù)時保持共享鎖以避免讀取已修改的數(shù)據(jù),但在事務(wù)結(jié)束前這些數(shù)據(jù)可能已更改,因此會導致不可重復的讀取和虛假數(shù)據(jù)。 |
| ReadCommitted | 發(fā)出共享鎖定并允許非獨占方式的鎖定。 |
| RepeatableRead | 在查詢中使用的所有數(shù)據(jù)上放置鎖,以防止其他用戶更新這些數(shù)據(jù)。這防止了不可重復的讀取,但仍有可能產(chǎn)生虛假行。 |
| Serializable | 默認級別,也是最高級別。表示事務(wù)完成前禁止外界更新數(shù)據(jù) |
| Chaos | 不使用隔離 |
| Snapshot |
需要注意服務(wù)端與客戶端必須使用同一級別的隔離模式,否則系統(tǒng)將會拋出FaultException異常。
服務(wù)類必須在至少一個方法開啟了事務(wù)后才可以設(shè)置隔離模式
1 public interface IService
2 {
3 [OperationContract]
4 void Method();
5 }
6
7 [ServiceBehavior(TransasctionIsolationLevel=IsolationLevel.ReadCommitted)]
8 public class Service : IService
9 {
10 //隱式事務(wù)
11 [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
12 public void Method()
13 {..........}
14 }
5.事務(wù)的超時
當事務(wù)實現(xiàn)隔離時,資源將會被鎖定,如果一些事務(wù)長期占有資源,那將容易造成死鎖,為了避免這個問題,事務(wù)有一個超時限制,這個超時默認值為60s。如果事務(wù)超過此時間,即使沒有發(fā)生異常,也會自動中止。
超時時候可以通過特性設(shè)置,也可使用*.config文件設(shè)置。下面的兩段代碼有著相同的效果,就是把超時時間設(shè)置為10s。
1 [ServiceBehavior(TransactionTimeout="00:00:10")]
2 public class Service:IService
3 {......}
4
5 <configuration>
6 ........
7 <system.serviceModel>
8 ........
9 <services>
10 <service name="MyService" behaviorConfiguration="myBehavior">
11 ......
12 </service>
13 </services>
14 <behaviors>
15 <serviceBehaviors>
16 <behavior name="myBehavior" transactionTimeout="00:00:10"/>
17 </serviceBehaviors>
18 </behaviors>
19 </system.serviceModel>
20 </configuration>
六、嵌套式事務(wù)
嵌套式事務(wù)經(jīng)常會出現(xiàn)在項目中,但往往容易被大家忽略,下面介紹一下 嵌套式事務(wù)的用法:
1 using (TransactionScope scope1 = new TransactionScope())
2 {
3 ..............
4 using (TransactionScope scope2=new TransactionScope(TransactionScopeOption.RequiresNew))
5 {
6 ..............
7 scope2.Complete(); //只完成嵌套式的內(nèi)部事務(wù),但事務(wù)并未正式提交
8 }
9 scope1.Complete(); //代表完成所有事務(wù),事務(wù)正式提交
10 }
一般項目中,大家都只會把事務(wù)用在DAL層,用于管理數(shù)據(jù)的CRUD,但其實在一些操作中,某些數(shù)據(jù)的操作必須具有一致性。比如在訂單管理中,當插入一條OrderItem時,Order表內(nèi)的總體價格,商品數(shù)量等也會隨之改變。很多人把兩個表的操作合成一個方法,放在OrderDAL中完成。但其實這樣做違返設(shè)計的原則,因為計算Order的總體價格時可能會包含商品優(yōu)惠、客戶等級、客戶積分等等業(yè)務(wù)邏輯,而在DAL層不應(yīng)該包含任何的業(yè)務(wù)邏輯存在的,所以這樣操作應(yīng)該放在業(yè)務(wù)層完成。這時候,業(yè)務(wù)層的方法內(nèi)就需要同時調(diào)用OrderItemDAL的AddOrderItem(OrderItem) 方法和OrderDAL的UpdateOrder(Order)方法,為了保證數(shù)據(jù)的一致性更新,就需要使用嵌套式事務(wù)。但這往往容易被開發(fā)人員所忽略,當Order表的更新成功而OrderItem表的插入失敗時,系統(tǒng)不能保證數(shù)據(jù)的同步回滾,那就會造成數(shù)據(jù)的邏輯性錯誤。
下面的例子就是為了保證數(shù)據(jù)一致性更新而使用的嵌套式事務(wù),在使用嵌套式事務(wù)的時候要應(yīng)該注意及其把對象釋放,避免做成死鎖。
1 namespace DAL
2 {
3 public class OrderDAL
4 {
5 public void UpdateOrder(Order order)
6 {
7 using (TransactionScope scope = new TransactionScope())
8 {
9 ......
10 scope.Complete();
11 }
12 }
13 }
14
15 public class OrderItemDAL
16 {
17 public void AddOrderItem(OrderItem orderItem)
18 {
19 using (TransactionScope scope = new TransactionScope())
20 {
21 ......
22 scope.Complete();
23 }
24 }
25 }
26 }
27
28 namespace BLL
29 {
30 public class OrderManager
31 {
32 public void AddOrderItem(OrderItem item)
33 {
34 using (TransactionScope scope = new TransactionScope())
35 {
36 OrderItemDAL orderItemDAL=new OrderItemDAL();
37 orderItemDAL.AddOrderItem(item);
38 OrderDAL orderDAL=new OrderDAL();
39 ........
40 orderDAL.UpdateOrder(order);
41 scope.Complete();
42 }
43 }
44 }
45 }
七、異步事務(wù)
記得在第二節(jié)的時候曾經(jīng)提起過事務(wù)類Transaction的方法中包含方法
public DependentTransaction DependentClone(DependentCloneOption)
此方法作用是克隆當前的事務(wù),它在多線程調(diào)用同一事務(wù)的情況下使用經(jīng)常使用。其中DependentCloneOption包含有兩個選項:
一為BlockCommitUntilComplete,這表示在依賴事務(wù)未完成前,事務(wù)將處于阻塞狀態(tài),只有在所有依賴事務(wù)完成后,事務(wù)才能執(zhí)行提交;
二為RollbackInNotComplete,這表示依賴事務(wù)必須在事務(wù)完成前調(diào)用Complete(),否則事務(wù)會被視為失敗。
在普通情況下,事務(wù)都會通過Transaction.Current 來獲取,但此方法只能獲取當前線程下的事務(wù)對象,在異步方法當中,這只會返回一個空值 null 。此時就需要使用DependentClone 方法獲取依賴事務(wù)對象 DependentTransaction ,再把此對象作為參數(shù)傳遞到回調(diào)函數(shù)中。
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Method();
6 Console.ReadKey();
7 }
8
9 static void Method()
10 {
11 using (TransactionScope scope = new TransactionScope())
12 {
13 ShowMessage("Main Thread");
14
15 //獲取一個依賴事務(wù),把依賴事務(wù)作為回調(diào)參數(shù)傳到回調(diào)函數(shù)中
16 DependentTransaction dependentTransaction=
17 Transaction.Current.DependentClone(DependentCloneOption.BlockCommitUntilComplete);
18 ThreadPool.QueueUserWorkItem(new WaitCallback(AsyncThread), dependentTransaction);
19 ........
20 scope.Complete(); //完成主線程事務(wù),在依賴事務(wù)完成前,事務(wù)提交將處于阻塞狀態(tài)
21 }
22 }
23
24 static void AsyncThread(object transaction)
25 {
26 //獲取依賴事務(wù),利用TransactionScope(Transaction)構(gòu)造函數(shù)生成隱式事務(wù)
27 DependentTransaction dependentTransaction = (DependentTransaction)transaction;
28 using (TransactionScope scope = new TransactionScope(dependentTransaction))
29 {
30 ShowMessage("AsyncThread");
31 ..........
32 scope.Complete(); //完成異步事務(wù)
33 }
34 //完成依賴事務(wù)
35 dependentTransaction.Complete();
36 }
37
38 static void ShowMessage(string data)
39 {
40 if (Transaction.Current != null)
41 {
42 Transaction transaction = Transaction.Current;
43 string info = string.Format("{0}:{1}\nTransaction:\n DistributedIndentifier:{2} \n LocalIndentifier:{3}\n",
44 data,Thread.CurrentThread.ManagedThreadId.ToString(),
45 transaction.TransactionInformation.DistributedIdentifier,
46 transaction.TransactionInformation.LocalIdentifier);
47 Console.WriteLine(info);
48 }
49 }
50 }
首先在主線程中利用Transaction.DependentClone(DependentCloneOption.BlockCommitUntilComplete) 方法生成一個依賴事務(wù),注意方法使用了BlockCommitUntilComplete的方式生成,即事務(wù)將在所有依賴事務(wù)使用Complete()后才能執(zhí)行提交。
然后利用ThreadPool.QueueUserWorkItem(WaitCallback,Object)方法把依賴事務(wù)作為回調(diào)參數(shù)傳遞到回調(diào)函數(shù)中。
最后在回調(diào)函數(shù)中使用TransactionScope(transaction)構(gòu)造函數(shù)生成對象,這代表把參數(shù)transaction作為當前的環(huán)境事務(wù)對象。觀察下面的運行結(jié)果,兩個線程中的事務(wù)都是同一個事務(wù)。

結(jié)束語
事務(wù)是在多個層次都會使用到的,但很多項目當中往往會忽略了這一點而只在數(shù)據(jù)層使用,在大型的系統(tǒng)當中這樣可能會影響到系統(tǒng)的一致性。特別是在分布式系統(tǒng)當中,操作往往同時存在于多個不同的系統(tǒng)當中,事務(wù)的處理更顯示出其重要性。
希望這篇文章能對大家的工作有幫助,對 .NET 開發(fā)有興趣的朋友歡迎加入QQ群:162338858 共同探討 !
C#綜合揭秘
通過修改注冊表建立Windows自定義協(xié)議
Entity Framework 并發(fā)處理詳解
細說進程、應(yīng)用程序域與上下文
細說多線程(上)
細說多線程(下)
細說事務(wù)
深入分析委托與事件
事務(wù)是在數(shù)據(jù)層、服務(wù)層、業(yè)務(wù)邏輯層多處地方都會使用到的東西,在本篇文章將會為大家一一細說。在軟件系統(tǒng)當中可以看到無論在數(shù)據(jù)庫、Web服務(wù)、WCF、文件系統(tǒng)都存在著數(shù)據(jù)參與到事務(wù)運作當中,我們把管理這些數(shù)據(jù)的工具稱為資源管理器RM(Resources Manager)。而事務(wù)管理器TM(Transaction Manager)就是協(xié)調(diào)多個資源管理器的工作,保證數(shù)據(jù)完整性的工具。
浙公網(wǎng)安備 33010602011771號