• 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 跨边界的事务流上下文

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


    作者:DanielWise
    出处:http://www.cnblogs.com/danielWise/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    C#颜色和名称样式对照表
    C#双缓冲技术
    用户自定义控件(含源代码)圆角Panel
    c#事件
    BackgroundWorker 转
    c#范型List的Sort方法详解
    C#double转化成字符串 保留小数位数
    错误提示之:无法激活请求的服务有关详细信息,请参见服务器的诊断跟踪日志
    错误提示之(MVC3.0):HTTP 404。您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确。
    SQL Server 2008远程服务连接
  • 原文地址:https://www.cnblogs.com/danielWise/p/1902579.html
Copyright © 2020-2023  润新知