第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里面可以找到它的定义:
成员名称 | 说明 |
---|---|
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 }
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 else15 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 : IService12 {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 }
在同一个应用程序域中,事务默认能相互传播,在上面的例子当中,当方法Method1()直接调用Mehtod2()的时候,事务也能够成功流转。
事务也能够在服务端与客户端之间传播,还能跨越服务边界,在多个系统当中流转,在WCF里把服务中的事务传播称作事务流(Transaction Flow)。如果事务流需要在服务端和客户端成功传播或使用分布式事务,必须具备以下条件:
- 绑 定必须支持事务,在WCF内并非所有绑定都支持事务,像常用的BasicHttpBinding就不支持事务的传播。只有以下几个绑定才能支持事务流的运 转:NetTcpBinding、WSHttpBinding、WSDualHttpBinding、WSFederationHttpBinding、 NetNamedPipeBinding。
- 服务方法必须设置好TransactionScopeRequired与TransactionAutoComplete两个事务的基本元素,要成功启动事务,这是基础条件。
- 把TransactionFlow设置为true,这代表启动事务流,允许在SOAP头部放置事务的信息。一般情况下TransactionFlow的默认值为false ,这表示事务只能在服务器的同一应用程序域内流转,而不能实现服务端与客户端之间的传播。
- 把服务契约的TransactionFlowOption设置为Allowed,这代表允许客户端的事务传播到服务端。
- 客户端必须启动一个事务,在最后使用TransactionScope.Complete ( ) 提交事务。
下面举几个例子来讲解一下事务流的使用方式。
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 : IExampleService11 {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 4142 43 7544 4945 46 4847 50 5751 5652 5553 54 58 7459 60 7362 6663 6564 67 68 7269 7170
客户端:
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 4243 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 }11144 7645 5046 47 4948 51 5852 5753 5654 55 59 7560 61 7463 6764 6665 68 69 7370 7271 112 113 ................... 114
客户端
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 56 ........ 7 8 ........ 9 2010 1411 ......12 1315 1916 1817
希望本篇文章对相关的开发人员有所帮助。
对JAVA与.NET开发有兴趣的朋友欢迎加入QQ群:162338858