WCF 支持分布式事务,也就是说事务可以跨越服务、进程、机器边界,在多个服务和客户端间存在.那么在WCF中如何使用分布式事务呢?

 

下面我们以一个例子来演示在wcf中使用分布式事务。下图是这个demo的整个solution:

为了便于测试,我们在建立一个临时表用于测试。表名为MGender.表中有两个字段:GenderCode,char(1),GenderDesc varchar(20).

在这个solution中WCFTrasactionServcies是contract 和Service。它是一个类库项目,在这里为了方便我们将interfact和实现interfact写在一个cs文件里,当然这种方法是不被推荐的。代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Data.SqlClient;
namespace WCFTrasactionServices
{
   
[ServiceContract(SessionMode=SessionMode.Required)]
    public interface IGenderInsert1
    {
        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Allowed)]
        void insertGerder1(string genderID, string genderDesc);
    }

    [ServiceContract(SessionMode=SessionMode.Required)]
    public interface IGenderInsert2
    {
        [OperationContract]
       
[TransactionFlow(TransactionFlowOption.Allowed)]
        void insertGerder2(string genderID, string genderDesc);
    }

    public class TestService1 : IGenderInsert1
    {

        #region IGenderInsert1 Members
        [OperationBehavior(TransactionScopeRequired = true)]
        public void insertGerder1(string genderID, string genderDesc)
        {
            using (SqlConnection conn = new SqlConnection("Data Source=localhost;DataBase=Test;integrated security=true"))
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand();
                cmd.CommandText = "insert into MGender (GenderCode,GenderDesc) values (@GenderCode,@GenderDesc)";
                cmd.Parameters.Add(new SqlParameter("@GenderCode", genderID));
                cmd.Parameters.Add(new SqlParameter("@GenderDesc", genderDesc));
                cmd.Connection = conn;
                cmd.ExecuteNonQuery();

            }
        }

        #endregion
    }

    public class TestService2 : IGenderInsert2
    {

        #region IGenderInsert2 Members
        [OperationBehavior(TransactionScopeRequired = true)]
        public void insertGerder2(string genderID, string genderDesc)
        {
            using (SqlConnection conn = new SqlConnection("Data Source=localhost;DataBase=Test;integrated security=true"))
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand();
                cmd.CommandText = "insert into MGender (GenderCode,GenderDesc) values (@GenderCode,@GenderDesc)";
                cmd.Parameters.Add(new SqlParameter("@GenderCode", genderID));
                cmd.Parameters.Add(new SqlParameter("@GenderDesc", genderDesc));
                cmd.Connection = conn;
                cmd.ExecuteNonQuery();
            }
        }
        #endregion
    }
}
TransactionFlowAttribute 只能用于服务方法(Operation/Method)上,它允许我们进行不同的事务参与设置。有一点要注意,我们不能为 IsOneWay=true 的服务设置事务支持。

TransactionFlowOption.NotAllowed: 不参与任何事务。(默认值)

TransactionFlowOption.Allowed: 允许参与事务。也就是说,如果调用方(客户端)和服务Binding启用了事务,则参与。

TransactionFlowOption.Mandatory: 强制启用事务。调用方(客户端)和服务 Binding 必须启用事务才能调用本服务。

WCFTrasactionHost是host,为了让WCF支持分布式事务,我们要修改Binding的一些属性。只有 TCP-、 IPC- 以及 WS-related 等 Binding 支持事务。缺省情况下,这些 Binding 并不会参与事务,需要我们显示将 TransactionFlow 属性设置为 true 才行。
我们可以通过配置文件来设置

<bindings>
  <netTcpBinding>
    <binding name = "Transactional" transactionFlow = "true" />
  </netTcpBinding>
</bindings>
当然也可以使用代码:
NetTcpBinding tcpBinding = new NetTcpBinding( );
tcpBinding.TransactionFlow = true;
WCFTrasactionHost是一个控制台应用程序,它的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCFTrasactionHost
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host1 = new ServiceHost(typeof(WCFTrasactionServices.TestService1));
            host1.Open();
            ServiceHost host2 = new ServiceHost(typeof(WCFTrasactionServices.TestService2));
            host2.Open();
            Console.WriteLine("endpoint is listenning");
            Console.ReadKey();
        }
    }
}
WCFTrasactionHost配置文件:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
    
  <service name ="WCFTrasactionServices.TestService1" behaviorConfiguration="TestBehavior1">
        <endpoint binding="netTcpBinding" bindingConfiguration="TransactionalTCP" contract="WCFTrasactionServices.IGenderInsert1" address="net.tcp://localhost/Test1"></endpoint>       
      </service>
      <service name ="WCFTrasactionServices.TestService2" behaviorConfiguration="TestBehavior2">
        <endpoint binding="netTcpBinding" contract="WCFTrasactionServices.IGenderInsert2" bindingConfiguration="TransactionalTCP" address="net.tcp://localhost/Test2"></endpoint>
      </service>
    </services>
    <bindings>
      <netTcpBinding>
       
<binding name="TransactionalTCP" transactionFlow="true"></binding>
      </netTcpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="TestBehavior1">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="
http://localhost/Test1"/>
        </behavior>
        <behavior name="TestBehavior2">
          <serviceMetadata httpGetEnabled="true" httpGetUrl="
http://localhost/Test2"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

WCFTrasactionClient:是client.我们使用svcutil 工具生成客户端代码,生成两个cs文件,TestService1.cs和TestService2.cs,当然我们也可以手工写客户端类。

WCFTrasactionClient代码:

   class Program
    {
        static void Main(string[] args)
        {
            GenderInsert1Client test1 = new GenderInsert1Client();
            GenderInsert2Client test2 = new GenderInsert2Client();
            using (TransactionScope scope = new TransactionScope())
            {
                try
                {
                
    test1.insertGerder1("M", "Male");
                    test2.insertGerder2("F"Female");
                    scope.Complete();
                    Console.WriteLine("Client has called the services.");                   
                }
                catch
                { //do noting
                }
                finally
                {
                    //(test1 as IDisposable).Dispose();
                    //(test2 as IDisposable).Dispose();
                }

            }
        }

这样我们就可以成功的往表MGender中插入两条数据。如果我们修改 :

  test1.insertGerder1("M", "Male"); test2.insertGerder2("F"Female");修改为 test1.insertGerder1("M", "Male"); test2.insertGerder2("FF"Female");

这样就会有异常,因为FF是两位字符,而GenderCode是char1型的,所以事务不成功,但第一条数据也成功插入,所以WCF会自动回滚第一插入的数据 test1.insertGerder1("M", "Male");

 

posted on 2008-11-21 11:52  John.Lau  阅读(2396)  评论(10编辑  收藏  举报