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

本文介绍如何在分布式系统中通过WS-AT协议实现跨服务边界的事务处理。重点讲解了使用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 跨边界的事务流上下文

[ServiceContract(SessionMode=SessionMode.Required)]
    public interface IBankService
    {
        [OperationContract]
        double GetBalance(string accountName);

        [OperationContract]
        void Transfer(string from, string to, double amount);
    }
    public class BankService : IBankService
    {
        [OperationBehavior(TransactionScopeRequired = false)]
        public double GetBalance(string accountName)
        {
            DBAccess dbAccess = new DBAccess();
            double amount = dbAccess.GetBalance(accountName);
            dbAccess.Audit(accountName, "Query", amount);
            return amount;
        }
        [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete=true)]
        public void Transfer(string from, string to, double amount)
        {
            try
            {
                Withdraw(from, amount);
                Deposit(to, amount);
            }
            catch(Exception ex)
            {
                throw ex;
            }
        }
        [OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        private void Withdraw(string accountName, double amount)
        {
            DBAccess dbAccess = new DBAccess();
            dbAccess.Withdraw(accountName, amount);
            dbAccess.Audit(accountName, "Withdraw", amount);
            OperationContext.Current.SetTransactionComplete();
        }
        [OperationBehavior(TransactionAutoComplete=false, TransactionScopeRequired=true)]
        private void Deposit(string accountName, double amount)
        {
            DBAccess dbAccess = new DBAccess();
            dbAccess.Deposit(accountName, amount);
            dbAccess.Audit(accountName, "Deposit", amount);
            OperationContext.Current.SetTransactionComplete();
        }
    }

    class DBAccess
    {
        private SqlConnection conn;
        public DBAccess()
        {
            string cs = ConfigurationManager.ConnectionStrings["sampleDB"].ConnectionString;
            conn = new SqlConnection(cs);
            conn.Open();
        }
        public void Deposit(string accountName, double amount)
        {
            string sql = string.Format("Deposit {0}, {1}", accountName, amount);
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.ExecuteNonQuery();
        }
        public void Withdraw(string accountName, double amount)
        {
            string sql = string.Format("Withdraw {0}, {1}", accountName, amount);
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.ExecuteNonQuery();
        }
        public double GetBalance(string accountName)
        {
            SqlParameter[] paras = new SqlParameter[2];
            paras[0] = new SqlParameter("@accountName", accountName);
            paras[1] = new SqlParameter("@sum", System.Data.SqlDbType.Float);
            paras[1].Direction = System.Data.ParameterDirection.Output;

            SqlCommand cmd = new SqlCommand();
            cmd.Connection = conn;
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
            cmd.CommandText = "GetBalance";

            for (int i = 0; i < paras.Length; i++)
            {
                cmd.Parameters.Add(paras[i]);
            }

            int n = cmd.ExecuteNonQuery();
            object o = cmd.Parameters["@sum"].Value;
            return Convert.ToDouble(o);
        }
        public void Audit(string accountName, string action, double amount)
        {
            Transaction txn = Transaction.Current;
            if (txn != null)
            {
                Console.WriteLine("{0} | {1} Audit:{2}",
                    txn.TransactionInformation.DistributedIdentifier,
                    txn.TransactionInformation.LocalIdentifier, action);
            }
            else
            {
                Console.WriteLine("<no transaction> Audit:{0}", action);
            }
            string sql = string.Format("Audit {0}, {1}, {2}",
                accountName, action, amount);
            SqlCommand cmd = new SqlCommand(sql, conn);
            cmd.ExecuteNonQuery();
        }
    }

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

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

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <connectionStrings>
    <!--Change connectionString refer to your environment-->
    <add connectionString="Data Source=SQL2K8CLUSTER\SQL2K8CLUSTER;Initial Catalog=BankService;Integrated Security=True" name="sampleDB"/>
  </connectionStrings>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="transactions" transactionFlow="true">
                    <security>
                        <transport>
                            <extendedProtectionPolicy policyEnforcement="Never" />
                        </transport>
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <behaviors>
            <serviceBehaviors>
                <behavior name="metadata">
                    <serviceMetadata httpGetEnabled="true" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <services>
            <service behaviorConfiguration="metadata" name="Services.BankService">
                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="transactions"
                    contract="Services.IBankService" />
                <host>
                    <baseAddresses>
                        <add baseAddress="http://localhost:8000/EssentialWCF" />
                    </baseAddresses>
                </host>
            </service>
        </services>
    </system.serviceModel>
</configuration>

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

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

            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                BankServiceClient proxy1 = new BankServiceClient();
                BankServiceClient proxy2 = new BankServiceClient();
                Console.WriteLine("{0}: Before - savings:{1}, checking {2}",
                    DateTime.Now,
                    proxy1.GetBalance("savings"),
                    proxy2.GetBalance("checking"));
                proxy1.Withdraw("savings", 100);
                proxy2.Deposit("checking", 100);
                scope.Complete();

                proxy1.Close();
                proxy2.Close();
            }
            BankServiceClient proxy3 = new BankServiceClient();
            Console.WriteLine("{0}: After - savings:{1}, checking {2}",
                DateTime.Now,
                proxy3.GetBalance("savings"),
                proxy3.GetBalance("checking"));
            proxy3.Close();

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

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

2010121112023346.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值