第4节主要讲述隐式事务的使用方式,TransactionScope是微软推荐使用的一个事务管理类,也是最常用的事务处理方式。
第5节主要讲述在WCF中,事务常用的方式。WCF服务中,事务是以方法为边界的,每个WCF服务的方法可以有独立事务的执行模式。而事务可以在多个服务中传播,也可以在服务端与客户端之间传播,介时事务管理器的级数将会晋升。

 

目录

一、事务的定义

二、事务管理器

三、在ADO.NET中实现事务

四、隐式事务 TransactionScope

五、在WCF中实现事务

六、嵌套式事务

七、异步事务

 

四、隐式事务 TransactionScope

1. TransactionScope的概念

TransactionScope存在于System.Transactions 命名空间中, 它是从Framework 2.0开始引入的一个事务管理类,它也是微软推荐使用的一个事务管理类。在TransactionScope的构造函数中会自动创建了一个新的LTM(轻 量级事务管理器),并通过Transaction.Current 隐式把它设置为环境事务。在使用隐式事务时,事务完成前程序应该调用TransactionScope的Complete()方法,把事务提交,最后利用 Dispose()释放事务对象。若执行期间出现错误,事务将自动回滚。

 1 public class TransactionScope:IDisposable
 2 {
 3     //多个构造函数
4 public TransactionScope(); 5 public TransactionScope(Transaction) 6 public TransactionScope(TransactionScopeOption) 7 ...... 8 public void Complete(); //提交事务
9 public void Dispose(); //释放事务对象
10 } 11 12 //调用方式
13 using(TransactionScope scope=new TransactionScope()) 14 { 15 //执行事务型工作
16 ............ 17 scope.Complete(); 18 }

 

2. TransactionScope的构造函数 TransactionScope(transactionScopeOption)

TransactionScopeOption 是枚举的一个实例,它主要用于TransactionScope的构造函数内,定义事务生成的状态要求。

在MSDN里面可以找到它的定义:

http://msdn.microsoft.com/zh-cn/library/system.transactions.transactionscopeoption.aspx

成员名称 说明
Required 该范围需要一个事务。 如果已经存在环境事务,则使用该环境事务。 否则,在进入范围之前创建新的事务。 这是默认值。
RequiresNew 总是为该范围创建新事务。
Suppress 环境事务上下文在创建范围时被取消。 范围中的所有操作都在无环境事务上下文的情况下完成。

 

 

 

 

 

这里Suppress有点特别,当使用Suppress范围内,所有的操作都将在无事务的上下文中执行,即当中的程序不再受到事务的保护,这大多数在嵌套式事务中使用。

 1 void DoWork()
 2  {
 3      using(TransactionScope scope=new TransactionScope())
 4      {
 5           //在事务环境中执行操作
6 ...... 7 NoTransaction(); 8 scope.Complete(); 9 } 10 } 11 12 void NoTransaction() 13 { 14 //在无事务环境中执行操作
15 using(TransactionScope scope=new TransactionScope(TransactionScopeOption.Suppress)) 16 { 17 ...... 18 } 19 }



3. 应用实例,在Entity Framework中使用TransactionScope

 1  public Order GetOrder(int id)
 2  {
 3      BusinessContext _context = new BusinessContext();
 4      Order order = null;
 5      try
 6      {
 7          using (TransactionScope scope = new TransactionScope())
 8          { 
 9              //数据操作
10 var list = _context.Order.Include("OrderItem") 11 .Where(x => x.ID == id); 12 if (list.Count() > 0) 13 order = list.First(); 14 else 15 order = new Order(); 16 scope.Complete(); //事务提交 17 } 18 } 19 catch (Exception ex) 20 { 21 ...... //出错处理,并返回一个空对象
22 order=new Order(); 23 } 24 _context.Dispose(); 25 return order; 26 }

回到目录

 

五、在WCF中实现事务

1. WCF服务中事务的边界

把WCF的事务边界独立成一节,是想大家注意这一点,WCF服务中,事务是以方法为边界的,每个WCF服务的方法可以有独立事务的执行模式。而事务可以在多个服务中传播,也可以在服务端与客户端之间传播,介时事务管理器的级数将会晋升。

 

2.简单的事务使用方式

TransactionScopeRequired与TransactionAutoComplete是WCF事务的基本元素。

当TransactionScopeRequired等于true时,代表在此WCF服务的方法中启动事务。反之,当此值为false时代表此方法不执行事务。

当 TransactionAutoComplete等于true时,代表该方法使用隐式事务,这也是微软推荐使用的方法。即当该方法在运行过程中没有抛出 Exception,操作就默认为完成,事务将自动提交。如果期间出现任何异常,事务就会自动回滚。如果TransactionAutoComplete 等于false时,该方法即为显式事务,即需要在方法完成时利用 OperationContext.Current.SetTransactionComplete () 显式提交事务。

 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     //隐式事务
14 [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)] 15 public void Method1() 16 { 17 ........... 18 } 19 20 //显式事务
21 [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=false)] 22 public void Method2() 23 { 24 ........... 25 OperationContext.Current.SetTransactionComplete(); 26 } 27 }


3. 事务的传播

在同一个应用程序域中,事务默认能相互传播,在上面的例子当中,当方法Method1()直接调用Mehtod2()的时候,事务也能够成功流转。

事务也能够在服务端与客户端之间传播,还能跨越服务边界,在多个系统当中流转,在WCF里把服务中的事务传播称作事务流(Transaction Flow)。如果事务流需要在服务端和客户端成功传播或使用分布式事务,必须具备以下条件:

  • 绑 定必须支持事务,在WCF内并非所有绑定都支持事务,像常用的BasicHttpBinding就不支持事务的传播。只有以下几个绑定才能支持事务流的运 转:NetTcpBinding、WSHttpBinding、WSDualHttpBinding、WSFederationHttpBinding、 NetNamedPipeBinding。
  • 服务方法必须设置好TransactionScopeRequired与TransactionAutoComplete两个事务的基本元素,要成功启动事务,这是基础条件。
  • 把TransactionFlow设置为true,这代表启动事务流,允许在SOAP头部放置事务的信息。一般情况下TransactionFlow的默认值为false ,这表示事务只能在服务器的同一应用程序域内流转,而不能实现服务端与客户端之间的传播。
  • 把服务契约的TransactionFlowOption设置为Allowed,这代表允许客户端的事务传播到服务端。
  • 客户端必须启动一个事务,在最后使用TransactionScope.Complete ( ) 提交事务。
TransactionFlowOption说明
TransactionFlowOption有三个选项:

一为NotAllowed,这代表了禁止客户端传播事务流到服务端,即使客户端启动了事务,该事务也会被忽略;
二为Allowed,这代表允许客户端的事务传播到服务端,但服务器端不一定会引用到此事务;
三为Mandatory,这代表服务端与客户端必须同时启动事务流,否则就会抛出InvalidOperationException异常。

 

下面举几个例子来讲解一下事务流的使用方式。

 3.1 在服务端与客户端之间传播事务

这 是事务流的基本使用方式,首先在服务端使用wsHttpBinding绑定建立一个服务契约,在方法中利用TransactionInformation 对象检测一下事务的状态。然后设置好TransactionScopeRequired与TransactionAutoComplete属性来启动事 务,在*.config中把TransactionFlow设置为true。再把服务的TransactionFlowOption设置为 Allowed,最后在客户端通过TransactionScope.Complete()的方法提交事务。

服务端:

 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         //使用隐式事务,并把TransactionFlowOption设置为Allowed打开事务流
13 [OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)] 14 [TransactionFlow(TransactionFlowOption.Allowed)] 15 public void Method1() 16 { 17 //通过TransactionInformation检测事务状态
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 //启动服务
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 <!--启动事务流--> 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 <!--使用支持事务流的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 //事务提交 15 scope.Complete(); 16 } 17 Console.ReadKey(); 18 } 19 20 //检查事务的状态
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 }

图 中上面代表服务端,下面代表客户端。可以看到,在客户端刚启动事务时,事务只一般的LTM轻量级事务,DistributedIndentifier为空 值。当调用ExampleService服务后,事务的级别便提升为分布式事务。而服务端与客户端的事务正是通过 DistributedIndentifier为标识的。

 
3.2使用分布式事务协调多个服务端的操作

在 分布式系统当中,单个客户端可能引用多个服务,分布式事务能协调多方的操作。多个系统中的操作要不同时成功,要不同时失败。下面的例子中,客户端同时引用 了ExampleService服务和ExtensionService服务,并启动了分布式事务。而在客户端与两个服务端的事务都是通过 DistributedIndentifier 作为事务的标识的。

服务端:

  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 //使用隐式事务,并把TransactionFlowOption设置为Allowed
14 [OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)] 15 [TransactionFlow(TransactionFlowOption.Allowed)] 16 public void Method1() 17 { 18 //通过TransactionInformation检测事务状态
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 //启动服务
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 <!--启动事务流--> 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 <!--使用支持事务流的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         //检查事务的状态
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 }

从测试结果可以看到在多个不同的服务端与客户端之间,都是通过DistributedIndentifier分布式事务ID来进行信息沟通的。一旦任何一出现问题,事务都会共同回滚,这对分布式开发是十分重要的。

值 得注意的一点,事务必须由客户端提交,当客户端调用无事务状态时,两个服务端的事务则无法进行辨认,即其中一方出错,事务出现回滚,另一方也无法感知,这 样容易引起逻辑性错误。试着把客户端代码改为 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress)){...},再运作一下,可以看到以下结果。事 务都是由两个服务端分别管理,系统并无启动分布式事务。

应该注意:分布式事务会耗费较大的资源,在没必要的情况下,应该尽量使用LTM级量级事务管理器,而避免使用DTC分布式事务管理。

 

4.事务的的隔离性

事务的隔离性是通过TransactionIsolationLevel来定义的,它存在以下几个级别:

Unspecified    
ReadUncommitted 在读取数据时保持共享锁以避免读取已修改的数据,但在事务结束前这些数据可能已更改,因此会导致不可重复的读取和虚假数据。
ReadCommitted 发出共享锁定并允许非独占方式的锁定。
RepeatableRead 在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。这防止了不可重复的读取,但仍有可能产生虚假行。
Serializable 默认级别,也是最高级别。表示事务完成前禁止外界更新数据
Chaos 不使用隔离  
Snapshot  

 

 

 

 

 

 

 

 

需要注意服务端与客户端必须使用同一级别的隔离模式,否则系统将会抛出FaultException异常。

服务类必须在至少一个方法开启了事务后才可以设置隔离模式

 1    public interface IService
 2    {
 3        [OperationContract]
 4        void Method();
 5    }
 6    
 7    [ServiceBehavior(TransasctionIsolationLevel=IsolationLevel.ReadCommitted)]
 8    public class Service : IService
 9    {
10       //隐式事务
11 [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)] 12 public void Method() 13 {..........} 14 }

 

5.事务的超时

当事务实现隔离时,资源将会被锁定,如果一些事务长期占有资源,那将容易造成死锁,为了避免这个问题,事务有一个超时限制,这个超时默认值为60s。如果事务超过此时间,即使没有发生异常,也会自动中止。

超时时候可以通过特性设置,也可使用*.config文件设置。下面的两段代码有着相同的效果,就是把超时时间设置为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>

回到目录

 希望本篇文章对相关的开发人员有所帮助。
对JAVA与.NET开发有兴趣的朋友欢迎加入QQ群:162338858 点击这里加入此群