第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  }

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              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  }

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 : 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  41  
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
62
63
64
65
66
67
68
69
70
71
72
73
74
75

客户端:

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   
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
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  5  
6 ........ 7
8 ........ 9
10
11 ......12
13
14
15
16
17
18
19
20

 希望本篇文章对相关的开发人员有所帮助。

对JAVA与.NET开发有兴趣的朋友欢迎加入QQ群:162338858