WCF 第五章 行为 事务-跨操作事务流

当在分布式系统中工作时,事务有时必须要跨越服务边界。例如,如果一个服务管理客户信息而另一个服务管理订单,一个客户提交一个订单并想产品可以发送到一个新的地址,系统将需要调用每个服务上的操作。如果事务完成,用户将会期待两个系统上的信息都被合适的更新。

  如果基础架构支持一个原子事务协议,服务可以像刚才描述的那样被组合到一个复合事务中。WS-AT(网络服务原子事务)提供在参与的服务间共享信息的平台来实现ACID事务必须的两步语义提交。在WCF中,在服务边界间的流事务信息被称作事务流。

  为了在服务边界间十万事务流转的语义,下面的5步必须实现:

  1. (服务契约) SessionMode.Required.  服务契约必须要求会话,因为这是信息如何在合作者和服务组成部分间共享消息的方式。

   2. (操作行为) TransactionScopeRequired=true. 操作行为必须要求一个事务范围。如果没有事务存在,那么将会按照要求创建一个新的事务。

   3.(操作契约) TransactionFlowOption.Allowed. 操作契约必须允许事务信息在消息头中流转。

   4.(绑定定义) TransactionFlow=true. 绑定必须使能事务流以便于信道可以将事务信息加到SOAP消息头中。也要注意绑定必须支持会话因为wsHttpBinding支持但是basicHttpBinding不支持。

  5.(客户端)TransactionScope. 这部分初始化事务,一般对客户端来说,当调用服务操作时必须使用一个事务范围。它也必须调用TransactionScope.Close() 来执行改变。

WCF 第五章 行为 事务-跨操作事务流_第1张图片

图片5.12 扩展服务边界的事务

  关于TransactionScopeRequired 属性的.NET 3.5 文档包含了下面的表来描述这些元素间的关系。为了方便我们在这里重述一遍。

TransactionScopeRequired 允许事务流的绑定 调用事务流 结果
False False No 方法不在事务内执行。
True False No 方法在一个新的事务中创建执行。
True or False False Yes 对这个事务头会返回一个SOAP错误。
False True Yes 方法不在事务内执行。
True True Yes 方法在事务内执行。

  列表5.18 描述了如何使用这些元素。代码与列表5.15 中显示的类似,5.15 中的代码是确定一个服务操作的事务实现,而5.18代码使用TransactionFlowOption 属性显示跨服务的事务实现。注意几个点。

首先,ServiceContract被标记为需要会话。为了实现这个需求,必须使用一个支持会话的协议,比如wsHttpBinding或者netTcpBinding。

其次,为了例证的目的,TransactionAutoComplete设置成false 同时方法的最后一行是SetTransactionComplete.如果执行达不到SetTransactionComplete,事务将自动回滚。

第三,TransactionFlowOption.Allowed 在每一个OperationContract 上设置来允许跨服务的事务调用。

列表5.18 跨边界的事务流上下文

001 [ServiceContract(SessionMode=SessionMode.Required)]
002     public interface IBankService
003     {
004         [OperationContract]
005         double GetBalance(string accountName);
006  
007         [OperationContract]
008         void Transfer(string from, string to, double amount);
009     }
010     public class BankService : IBankService
011     {
012         [OperationBehavior(TransactionScopeRequired = false)]
013         public double GetBalance(string accountName)
014         {
015             DBAccess dbAccess = new DBAccess();
016             double amount = dbAccess.GetBalance(accountName);
017             dbAccess.Audit(accountName, "Query", amount);
018             return amount;
019         }
020         [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete=true)]
021         public void Transfer(string from, string to, double amount)
022         {
023             try
024             {
025                 Withdraw(from, amount);
026                 Deposit(to, amount);
027             }
028             catch(Exception ex)
029             {
030                 throw ex;
031             }
032         }
033         [OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]
034         [TransactionFlow(TransactionFlowOption.Allowed)]
035         private void Withdraw(string accountName, double amount)
036         {
037             DBAccess dbAccess = new DBAccess();
038             dbAccess.Withdraw(accountName, amount);
039             dbAccess.Audit(accountName, "Withdraw", amount);
040             OperationContext.Current.SetTransactionComplete();
041         }
042         [OperationBehavior(TransactionAutoComplete=false, TransactionScopeRequired=true)]
043         private void Deposit(string accountName, double amount)
044         {
045             DBAccess dbAccess = new DBAccess();
046             dbAccess.Deposit(accountName, amount);
047             dbAccess.Audit(accountName, "Deposit", amount);
048             OperationContext.Current.SetTransactionComplete();
049         }
050     }
051  
052     class DBAccess
053     {
054         private SqlConnection conn;
055         public DBAccess()
056         {
057             string cs = ConfigurationManager.ConnectionStrings["sampleDB"].ConnectionString;
058             conn = new SqlConnection(cs);
059             conn.Open();
060         }
061         public void Deposit(string accountName, double amount)
062         {
063             string sql = string.Format("Deposit {0}, {1}", accountName, amount);
064             SqlCommand cmd = new SqlCommand(sql, conn);
065             cmd.ExecuteNonQuery();
066         }
067         public void Withdraw(string accountName, double amount)
068         {
069             string sql = string.Format("Withdraw {0}, {1}", accountName, amount);
070             SqlCommand cmd = new SqlCommand(sql, conn);
071             cmd.ExecuteNonQuery();
072         }
073         public double GetBalance(string accountName)
074         {
075             SqlParameter[] paras = new SqlParameter[2];
076             paras[0] = new SqlParameter("@accountName", accountName);
077             paras[1] = new SqlParameter("@sum", System.Data.SqlDbType.Float);
078             paras[1].Direction = System.Data.ParameterDirection.Output;
079  
080             SqlCommand cmd = new SqlCommand();
081             cmd.Connection = conn;
082             cmd.CommandType = System.Data.CommandType.StoredProcedure;
083             cmd.CommandText = "GetBalance";
084  
085             for (int i = 0; i < paras.Length; i++)
086             {
087                 cmd.Parameters.Add(paras[i]);
088             }
089  
090             int n = cmd.ExecuteNonQuery();
091             object o = cmd.Parameters["@sum"].Value;
092             return Convert.ToDouble(o);
093         }
094         public void Audit(string accountName, string action, double amount)
095         {
096             Transaction txn = Transaction.Current;
097             if (txn != null)
098             {
099                 Console.WriteLine("{0} | {1} Audit:{2}",
100                     txn.TransactionInformation.DistributedIdentifier,
101                     txn.TransactionInformation.LocalIdentifier, action);
102             }
103             else
104             {
105                 Console.WriteLine(" Audit:{0}", action);
106             }
107             string sql = string.Format("Audit {0}, {1}, {2}",
108                 accountName, action, amount);
109             SqlCommand cmd = new SqlCommand(sql, conn);
110             cmd.ExecuteNonQuery();
111         }
112     }

   列表5.19 显示了配置文件。注意绑定是支持会话的wsHttpBinding。因为代码在服务契约中声明了SessionMode.Required 所以这是必须的。也要注意transactionFlow=”true”在绑定配置部分定义。

列表5.19 在配置文件中使能事务流

01 "1.0" encoding="utf-8" ?>
02
03   
04     
05     "Data Source=SQL2K8CLUSTER\SQL2K8CLUSTER;Initial Catalog=BankService;Integrated Security=True" name="sampleDB"/>
06   
07     
08         
09             
10                 "transactions" transactionFlow="true">
11                     
12                         
13                             "Never" />
14                         
15                     
16                 
17             
18         
19         
20             
21                 "metadata">
22                     "true" />
23                 
24             
25         
26         
27             "metadata" name="Services.BankService">
28                 "" binding="wsHttpBinding" bindingConfiguration="transactions"
29                     contract="Services.IBankService" />
30                 
31                     
32                         "http://localhost:8000/EssentialWCF" />
33                     
34                 
35             
36         
37     
38

  列表5.20 显示了将两个服务的工作合并到一个单独事务的客户端代码。创建了三个代理,两个指向一个服务,第三个指向另外一个服务。两个查询操作和一个 Withdraw 操作在proxy1上调用,然后在proxy2上调用Deposit。如果在那些服务操作内所有事情都很顺利,它们每个都会执行自己的 SetTransactionComplete().在两个操作都返回后,客户端调用scope.Complete()来完成事务。只有事务中所有部分都 执行它们的SetTransactionComplete()方法,事务才会被提交;如果它们中有没有成功的,整个事务将会被回滚。最后,proxy3会 调用两个查询操作来确定经过事务处理以后改变是一致的。

列表5.20 在一个客户端合作完成一个分布式事务

01 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
02 {
03     BankServiceClient proxy1 = new BankServiceClient();
04     BankServiceClient proxy2 = new BankServiceClient();
05     Console.WriteLine("{0}: Before - savings:{1}, checking {2}",
06         DateTime.Now,
07         proxy1.GetBalance("savings"),
08         proxy2.GetBalance("checking"));
09     proxy1.Withdraw("savings", 100);
10     proxy2.Deposit("checking", 100);
11     scope.Complete();
12  
13     proxy1.Close();
14     proxy2.Close();
15 }
16 BankServiceClient proxy3 = new BankServiceClient();
17 Console.WriteLine("{0}: After - savings:{1}, checking {2}",
18     DateTime.Now,
19     proxy3.GetBalance("savings"),
20     proxy3.GetBalance("checking"));
21 proxy3.Close();

  图片5.13 显示了一个客户端和两个服务端的输出。左边的客户端打印了savings账户的总额并在转账前后检查。两个服务端在右边。最上面的服务被Proxy1和 Proxy3访问;底下的被Proxy2访问。上面的服务执行了两个查询操作,一个Withdraw操作和另外两个查询操作。下面的服务执行了一个 Deposit操作。注意分布式事务身份id 在两个服务端都是一致的,意味着它们都是同一个事务的一部分。

图片5.13 一个单一食物中的两个合作的事务服务的输出

WCF 第五章 行为 事务-跨操作事务流_第2张图片


 

=========

转载自

作者: DanielWise
出处: http://www.cnblogs.com/danielWise/
 

转载于:https://www.cnblogs.com/llbofchina/archive/2011/06/30/2094083.html

你可能感兴趣的:(WCF 第五章 行为 事务-跨操作事务流)