zoukankan      html  css  js  c++  java
  • 如何创建一个简单的基于OleTx协议的WCF事务实例

        由于工作需要,最近研究了一下WCF分布式事务方面的技术,自己做了一个基于OleTx协议的WCF事务实例,供大家参考。

        一、实例背景

        本实例以大家熟悉的银行转账业务为背景,在几台不同的机器上部署一套基于OleTx协议的WCF分布式服务。

        本例需要A、B、C、D四台计算机,其中A表示SqlServer服务器,B部署取款服务,C部署存款服务,D部署客户端程序(调用存款和取款服务),D同时直接A进行日志写入。一次转账操作分为三个部分:存款、取款、写日志,这三部分组成一个转账事务,要么全部成功,要么全部失败。

        二、WCF服务端代码

        创建一个WCF服务项目OleTxService。

        1、契约

        取款服务契约如下:

     1 using System.Net.Security;
     2 using System.ServiceModel;
     3 using OleTxService.Models;
     4 
     5 namespace OleTxService
     6 {
     7     /// <summary>
     8     /// 取款服务接口
     9     /// </summary>
    10     [ServiceContract(SessionMode = SessionMode.Required)]
    11     public interface IWithDrawService
    12     {
    13         /// <summary>
    14         /// 取款操作
    15         /// </summary>
    16         /// <param name="accountId">取款账号</param>
    17         /// <param name="amount">取款金额</param>
    18         [OperationContract]
    19         [TransactionFlow(TransactionFlowOption.Mandatory)]
    20         [FaultContract(typeof(MyFault), ProtectionLevel = ProtectionLevel.None)]
    21         void Withdraw(string accountId, double amount);
    22     }
    23 }
    View Code

        存款服务契约如下:

     1 using System.Net.Security;
     2 using System.ServiceModel;
     3 using OleTxService.Models;
     4 
     5 namespace OleTxService
     6 {
     7     /// <summary>
     8     /// 存款服务接口
     9     /// </summary>
    10     [ServiceContract(SessionMode = SessionMode.Required)]
    11     public interface IDepositService
    12     {
    13         /// <summary>
    14         /// 存款操作
    15         /// </summary>
    16         /// <param name="accountId">存款账号</param>
    17         /// <param name="amount">存款金额</param>
    18         [OperationContract]
    19         [FaultContract(typeof(MyFault), ProtectionLevel = ProtectionLevel.None)]
    20         [TransactionFlow(TransactionFlowOption.Mandatory)]
    21         void Deposit(string accountId, double amount);
    22     }
    23 }
    View Code

        由于后面要设置事务操作行为的TransactionAutoComplete属性为false,所以要求其中契约的Session要设置为SessionMode.Required。

        在操作上要增加一个TransactionFlow特性,允许事务在客户端到服务端之间流转。

        2、服务实现

        取款服务如下:

     1 using System;
     2 using System.Configuration;
     3 using System.Data;
     4 using System.Data.SqlClient;
     5 using System.ServiceModel;
     6 using OleTxService.Helper;
     7 using OleTxService.Models;
     8 
     9 namespace OleTxService
    10 {
    11     /// <summary>
    12     /// 取款服务
    13     /// </summary>
    14     public class WithDrawService : IWithDrawService
    15     {
    16         /// <summary>
    17         /// 取款操作
    18         /// </summary>
    19         /// <param name="accountId">取款账号</param>
    20         /// <param name="amount">取款金额</param>
    21         [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
    22         public void Withdraw(string accountId, double amount)
    23         {
    24             string connString = ConfigurationManager.ConnectionStrings["MyBank"].ConnectionString;
    25             try
    26             {
    27                 var _parms = new SqlParameter[]
    28                 {
    29                     SQLServerHelper.BuildInParameter("id", SqlDbType.VarChar, accountId),
    30                     SQLServerHelper.BuildInParameter("amount", SqlDbType.Float, amount)
    31                 };
    32                 SQLServerHelper.ExecuteNonQuery(connString, CommandType.StoredProcedure, "P_WITHDRAW", _parms);
    33                 OperationContext.Current.SetTransactionComplete();
    34             }
    35             catch (Exception ex)
    36             {
    37                 throw new FaultException<MyFault>(new MyFault(string.Format("取款异常:{0}", ex.Message)), "取款出错");
    38             }
    39         }
    40     }
    41 }
    View Code

        存款服务如下:

     1 using System;
     2 using System.Configuration;
     3 using System.Data;
     4 using System.Data.SqlClient;
     5 using System.ServiceModel;
     6 using OleTxService.Helper;
     7 using OleTxService.Models;
     8 
     9 namespace OleTxService
    10 {
    11     /// <summary>
    12     /// 存款服务
    13     /// </summary>
    14     public class DepositService : IDepositService
    15     {
    16         /// <summary>
    17         /// 存款操作
    18         /// </summary>
    19         /// <param name="accountId">存款账号</param>
    20         /// <param name="amount">存款金额</param>
    21         [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
    22         public void Deposit(string accountId, double amount)
    23         {
    24             string connString = ConfigurationManager.ConnectionStrings["MyBank"].ConnectionString;
    25             try
    26             {
    27                 var _parms = new SqlParameter[]
    28                 {
    29                     SQLServerHelper.BuildInParameter("id", SqlDbType.VarChar, accountId),
    30                     SQLServerHelper.BuildInParameter("amount", SqlDbType.Float, amount)
    31                 };
    32                 SQLServerHelper.ExecuteNonQuery(connString, CommandType.StoredProcedure, "P_DEPOSIT", _parms);
    33                 OperationContext.Current.SetTransactionComplete();
    34             }
    35             catch (Exception ex)
    36             {
    37                 throw new FaultException<MyFault>(new MyFault(string.Format("存款异常:{0}", ex.Message)), "存款出错");
    38             }
    39         }
    40     }
    41 }
    View Code

        其中,取款和存款的具体代码未贴出,读者可以自己实现。具体操作:从银行账户表的一个账号(一行数据)减少金额,另一个账号增加金额。

        注意,在服务操作上增加了一个操作行为:[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]。TransactionScopeRequired=true表示该服务操作需要在一个显示的TransactionScope中。TransactionAutoComplete=false表示必须手工完成该事务,其方法是在要完成事务的地方写上:OperationContext.Current.SetTransactionComplete();

        3、配置WCF服务

        在配置文件中配置这两个服务,有关WCF服务的配置代码如下:

      <system.serviceModel>
        <bindings>
          <netTcpBinding>
            <binding name="transactionBinding" transactionFlow="true" transactionProtocol="OleTransactions" />
          </netTcpBinding>
        </bindings>
        <services>
          <service name="OleTxService.DepositService">
            <endpoint address="deposit" binding="netTcpBinding" bindingConfiguration="transactionBinding"
              contract="OleTxService.IDepositService" />
          </service>
          <service name="OleTxService.WithDrawService">
            <endpoint address="withdraw" binding="netTcpBinding" bindingConfiguration="transactionBinding"
              contract="OleTxService.IWithDrawService" />
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior>
              <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
              <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
              <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
              <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <protocolMapping>
            <add binding="basicHttpsBinding" scheme="https" />
        </protocolMapping>    
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
      </system.serviceModel>
    View Code

        我们将服务配置成netTcpBinding,并在绑定配置中加上允许事务流转、指定事务协议的配置项:

        <bindings>
          <netTcpBinding>
            <binding name="transactionBinding" transactionFlow="true" transactionProtocol="OleTransactions" />
          </netTcpBinding>
        </bindings>
    View Code

        三、在IIS中部署netTcpBinding的WCF服务

        由于我们的服务是采用netTcpBinding,在IIS中寄宿时需要有些注意的地方。

        1、部署网站

        在IIS中新增一个网站oletransaction,用来部署本例中的服务。在本例中,服务终结点中的地址配置的是相对地址,也可以为空。

        2、编辑绑定

        在IIS中寄宿非HTTP协议的WCF服务时,需要手动为网站编辑相应的绑定。在本例中,需要为服务增加一个net.tcp绑定,并在其中指定绑定端口。

        在IIS中选中网站,右击“编辑绑定(Edit Bindings...)”,在弹出的“站点绑定(Site Bindings)”对话框中添加一个net.tcp绑定。

           

        其中,绑定信息“8060:*”表示设置TCP的监听端口为8060。

        3、添加net.tcp协议支持

        需要手动在网站中添加对net.tcp协议的支持。

        选中网站,点击“高级设置(Advanced Setting...)”。

        在弹出的高级设置窗口中找到“启用的协议(Enabled Protocols)”,添加net.tcp,协议之间以逗号分隔。

        4、浏览服务

        在完成编辑绑定、添加协议支持后,即可浏览服务:

        如何不能访问,请检查防火墙是否关闭,Net Tcp监听服务是否打开。

        照此方式在B、C服务器中分别部署取款服务、存款服务。

        四、WCF客户端代码

        1、引用服务

        根据服务的WSDL地址分别引用取款服务、取款服务。

        2、编写转账代码

        转账的总体代码如下:

                    using (var scope = new TransactionScope())
                    {
                        try
                        {
                            Transfer(fromAccountId, toAccountId, amount);
                            var logContent = string.Format("完成一条转账操作,转出账号:{0},转入账号:{1},转账金额:{2}", fromAccountId, toAccountId,
                                amount);
                            WriteLog(logContent, remark);
                            scope.Complete();
                            Console.WriteLine("转账成功!");
                        }
                        catch (FaultException<withdraw.MyFault> ex)
                        {
                           Console.WriteLine("转账失败:{0},错误详情:{1}", ex.Message, ex.Detail.ErrorMessage); 
                        }
                        catch (FaultException<deposit.MyFault> ex)
                        {
                            Console.WriteLine("转账失败:{0},错误详情:{1}", ex.Message, ex.Detail.ErrorMessage); 
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("转账失败:{0}", ex.Message);
                        }
                    }
    View Code

        其中转账方法Transfer定义如下:

            /// <summary>
            /// 转账操作
            /// </summary>
            /// <param name="fromAccountId">转出账号</param>
            /// <param name="toAccountId">转入账号</param>
            /// <param name="amount">转账金额</param>
            static void Transfer(string fromAccountId, string toAccountId, double amount)
            {
                withdraw.WithDrawServiceClient clientWithdraw = new WithDrawServiceClient("NetTcpBinding_IWithDrawService");
                clientWithdraw.Withdraw(fromAccountId, amount);
                deposit.DepositServiceClient clientDeposit = new DepositServiceClient("NetTcpBinding_IDepositService");
                clientDeposit.Deposit(toAccountId, amount);
    
            }
    View Code

        写日志的代码如下:

            /// <summary>
            /// 记录日志
            /// </summary>
            /// <param name="content">日志内容</param>
            /// <param name="remark">备注</param>
            static void WriteLog(string content, string remark)
            {
                var sql = ConfigurationManager.AppSettings["InsertBankRecord"];
                var _params = new SqlParameter[]
                {
                    SQLServerHelper.BuildInParameter("ID", SqlDbType.UniqueIdentifier, Guid.NewGuid()),
                    SQLServerHelper.BuildInParameter("Content", SqlDbType.NVarChar, content),
                    SQLServerHelper.BuildInParameter("Remark", SqlDbType.NVarChar, remark ?? string.Empty),
                    SQLServerHelper.BuildInParameter("UpdateTime", SqlDbType.DateTime, DateTime.Now)
                };
                SQLServerHelper.ExecuteNonQuery(_myConnString, CommandType.Text, sql, _params);
            }
    View Code

        在转账的总体代码中,将调用取款服务、存款服务、写日志这三个操作放到一个TransactionScope变量scope中。只有当三个操作本身正常完成,并且调用scope.Complete()后,事务成功,否则事务失败。

        3、部署客户端

        将客户端部署到D计算机,并配置好服务地址、数据库连接串。

        五、配置MSDTC

        由于本例采用的是基于OleTx协议的WCF事务,用到了微软的MSDTC服务,因此,需要配置MSDTC服务。

        1、配置MSDTC

        在“管理工具(Administrative Tools)”中打开“组件服务(Component Services)”。

        找到我的计算机节点:“组件服务(Component Services)”=>“计算机(Computers)”=>“我的计算机(My Computer)”,右键点击“属性(Properties)”。

        在“MSDTC”选项卡中,勾选“使用本地协调器(Use local coordinator)”选项。

        2、配置Local DTC

        还需要配置“本地DTC(Local DTC)”。

        在组件服务中找到本地DTC节点:组件服务(Component Services)=>计算机(Computers)=>我的计算机(My Computer)=>分布式事务协调器(Distributed Transaction Coordinator),右击“属性(Properties)”。

        在弹出的本地DTC属性窗口中,点击“安全(Security)”选项卡,配置成下面的值。

        六、验证事务

        部署好整个WCF分布式事务系统后,运行程序,验证事务正确性:

    • 正常输入情况下应该完成整个事务
    • 调用服务错误情况下应该回滚整个事务
    • 本地写日志出错情况下应该回滚整个事务

        七、总结

        本文完成了一个基于OleTx协议的WCF分布式事务的代码编写和系统部署的过程。总共有三大要点:

    • WCF事务编程,主要注意契约和操作行为中的一些关于事务的特性。
    • 在IIS中部署基于netTcpBinding的WCF服务,主要关注编辑站点绑定、添加net.tcp协议支持。
    • 配置MSDTC和本地DTC,这是部署WCF分布式事务的必要条件。
  • 相关阅读:
    python学习(八) 异常
    python学习(七) 更加抽象
    java面试(五)
    python学习(六) 抽象
    python学习(五) 条件、循环和其他语句
    JAVA基础知识——IO
    Java进阶之路——从初级程序员到架构师,从小工到专家(转载)
    如何成为java架构师(转载)
    python学习(四) 字典:当索引不好用时
    MVC Html.DropDownList 和DropDownListFor 的常用方法
  • 原文地址:https://www.cnblogs.com/huatao/p/4661787.html
Copyright © 2011-2022 走看看