第4节主要讲述隐式事务的使用方式,TransactionScope是微软推荐使用的一个事务管理类,也是最常用的事务处理方式。
第5节主要讲述在WCF中,事务常用的方式。WCF服务中,事务是以方法为边界的,每个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有三个选项:
一为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>
转载于:https://blog.51cto.com/79100812/841452