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允许事务流的绑定调用事务流结果
FalseFalseNo方法不在事务内执行。
TrueFalseNo方法在一个新的事务中创建执行。
True or FalseFalseYes对这个事务头会返回一个SOAP错误。
FalseTrueYes方法不在事务内执行。
TrueTrueYes方法在事务内执行。

  列表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 一个单一食物中的两个合作的事务服务的输出


 

=========

转载自

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值