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() 来执行改变。

12-10-2010 5-59-33 PM

图片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("<no transaction> 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<?xml version="1.0" encoding="utf-8" ?>
02<configuration>
03  <connectionStrings>
04    <!--Change connectionString refer to your environment-->
05    <add connectionString="Data Source=SQL2K8CLUSTER\SQL2K8CLUSTER;Initial Catalog=BankService;Integrated Security=True" name="sampleDB"/>
06  </connectionStrings>
07    <system.serviceModel>
08        <bindings>
09            <wsHttpBinding>
10                <binding name="transactions" transactionFlow="true">
11                    <security>
12                        <transport>
13                            <extendedProtectionPolicy policyEnforcement="Never" />
14                        </transport>
15                    </security>
16                </binding>
17            </wsHttpBinding>
18        </bindings>
19        <behaviors>
20            <serviceBehaviors>
21                <behavior name="metadata">
22                    <serviceMetadata httpGetEnabled="true" />
23                </behavior>
24            </serviceBehaviors>
25        </behaviors>
26        <services>
27            <service behaviorConfiguration="metadata" name="Services.BankService">
28                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="transactions"
29                    contract="Services.IBankService" />
30                <host>
31                    <baseAddresses>
32                        <add baseAddress="http://localhost:8000/EssentialWCF" />
33                    </baseAddresses>
34                </host>
35            </service>
36        </services>
37    </system.serviceModel>
38</configuration>

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

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

01using (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}
16BankServiceClient proxy3 = new BankServiceClient();
17Console.WriteLine("{0}: After - savings:{1}, checking {2}",
18    DateTime.Now,
19    proxy3.GetBalance("savings"),
20    proxy3.GetBalance("checking"));
21proxy3.Close();

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

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


 

=========

转载自

 

posted @ 2011-06-30 09:54  Gavin Liu  阅读(224)  评论(0编辑  收藏  举报

Right people get the right information at the right time.
以技术求生存,以市场求发展;学以至用,开拓创新;达技术之颠峰,至市场之广阔!