[WCF Transaction] 3. 事务投票

来源:互联网 发布:中国联通wap网络 编辑:程序博客网 时间:2024/05/17 08:03

我们知道事务是通过参与方进行投票(Voting)来决定 "提交(Complete)" 或者 "回滚(Rollback)"操作的。默认情况下,WCF 通过 OperationBehavior(TransactionAutoComplete = true)来完成投票动作。(TransactionAutoComplete = true 是缺省值,不需要显式声明。)

我们还是使用第 2 章的例子,将服务方法默认的 TransactionAutoComplete=true 改为 false,看看结果 。

// ---- Service1 -----

[ServiceContract(SessionMode=SessionMode.Required)]
public interface IService1
{
  [OperationContract]
  [TransactionFlow(TransactionFlowOption.Allowed)]
  void Test();
}

public class MyService1 : IService1
{
  [OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=false)]
  public void Test()
  {
    string connStr = "server=(local);uid=sa;pwd=sa;database=temp";
    using (SqlConnection conn = new SqlConnection(connStr))
    {
      conn.Open();

      SqlCommand cmd = new SqlCommand("insert into [User] ([name]) values (@name)",
        conn);
        
      cmd.Parameters.Add(new SqlParameter("@name", "ZhangSan"));
      cmd.ExecuteNonQuery();
    }
  }
}

// ---- Service2 -----

[ServiceContract(SessionMode = SessionMode.Required)]
public interface IService2
{
  [OperationContract]
  [TransactionFlow(TransactionFlowOption.Allowed)]
  void Test();
}

public class MyService2 : IService2
{
  [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
  public void Test()
  {
    string connStr = "server=(local);uid=sa;pwd=sa;database=temp";
    using (SqlConnection conn = new SqlConnection(connStr))
    {
      conn.Open();

      SqlCommand cmd = new SqlCommand("insert into Account ([user], [money]) values (@user, @money)",
        conn);
        
      cmd.Parameters.Add(new SqlParameter("@user", "ZhangSan"));
      cmd.Parameters.Add(new SqlParameter("@money", 100));
      cmd.ExecuteNonQuery();
    }
  }
}

public class WcfTest
{
  public static void Test()
  {

    // ---- Host -----

    AppDomain.CreateDomain("Server").DoCallBack(delegate
    {
      NetTcpBinding bindingServer = new NetTcpBinding();
      bindingServer.TransactionFlow = true;

      ServiceHost host1 = new ServiceHost(typeof(MyService1), new Uri("net.tcp://localhost:8080"));
      host1.AddServiceEndpoint(typeof(IService1), bindingServer, "");
      host1.Open();

      ServiceHost host2 = new ServiceHost(typeof(MyService2), new Uri("net.tcp://localhost:8081"));
      host2.AddServiceEndpoint(typeof(IService2), bindingServer, "");
      host2.Open();
    });

    // ---- Client -----

    NetTcpBinding bindingClient = new NetTcpBinding();
    bindingClient.TransactionFlow = true;

    IService1 client1 = ChannelFactory<IService1>.CreateChannel(bindingClient,
      new EndpointAddress("net.tcp://localhost:8080"));
    IService2 client2 = ChannelFactory<IService2>.CreateChannel(bindingClient,
      new EndpointAddress("net.tcp://localhost:8081"));

    using (TransactionScope scope = new TransactionScope())
    {
      try
      {
        client1.Test();
        client2.Test();

        scope.Complete();
      }
      finally
      {
        (client1 as IDisposable).Dispose();
        (client2 as IDisposable).Dispose();
      }
    }
  }
}


运行结果表明事务无法提交,触发 TransactionAbortedException 异常,显示 "事务终止"。那么除了默认被称之为"声明投票(Declarative voting)" 的方式外,我们还能怎么做?OperationContext 有个SetTransactionComplete()方法,允许我们在代码中完成投票行为。这种投票方式更加灵活,便于我们在代码中做出更多的控制,被称之为 "显式投票(Explicitvoting)"。

在上面两个 Test() 方法的最后一行,添加 "OperationContext.Current.SetTransactionComplete();",再次运行,事务被正确提交。

 
[OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=false)]
public void Test()
{
  // ...

  OperationContext.Current.SetTransactionComplete();
}

...


接下来,我们设想另外一种情况。事务不由 Client 发起,在 Service1.Test() 调用Service2.Test(),那么事务会是个什么样子呢?Service1、Service2 的参数"OperationBehavior(TransactionScopeRequired = true)"决定了如果没有外界传入的环境事务,那么会自动创建一个根事务。所以 Service1.Test() 会创建一个根事务,而Service2.Test() 会参与这个事务。可问题在于 Service.Test() 中并没有显示调用Transaction.Complete,事务能被提交吗?

 
// ---- Service1 -----

[ServiceContract]
public interface IService1
{
  [OperationContract]
  [TransactionFlow(TransactionFlowOption.Allowed)]
  void Test();
}

public class MyService1 : IService1
{
  [OperationBehavior(TransactionScopeRequired=true)]
  public void Test()
  {
    string connStr = "server=(local);uid=sa;pwd=sa;database=temp";
    using (SqlConnection conn = new SqlConnection(connStr))
    {
      conn.Open();

      SqlCommand cmd = new SqlCommand("insert into [User] ([name]) values (@name)",
        conn);
        
      cmd.Parameters.Add(new SqlParameter("@name", "ZhangSan"));
      cmd.ExecuteNonQuery();
    }

    InvokeService2();
  }

  public void InvokeService2()
  {
    NetTcpBinding bindingClient = new NetTcpBinding();
    bindingClient.TransactionFlow = true;

    IService2 client2 = ChannelFactory<IService2>.CreateChannel(bindingClient,
      new EndpointAddress("net.tcp://localhost:8081"));
      
    using (client2 as IDisposable)
    {
      client2.Test();
    }
  }
}

// ---- Service2 -----

[ServiceContract]
public interface IService2
{
  [OperationContract]
  [TransactionFlow(TransactionFlowOption.Allowed)]
  void Test();
}

public class MyService2 : IService2
{
  [OperationBehavior(TransactionScopeRequired = true)]
  public void Test()
  {
    string connStr = "server=(local);uid=sa;pwd=sa;database=temp";
    using (SqlConnection conn = new SqlConnection(connStr))
    {
      conn.Open();

      SqlCommand cmd = new SqlCommand("insert into Account ([user], [money]) values (@user, @money)",
        conn);
 
        
      cmd.Parameters.Add(new SqlParameter("@user", "ZhangSan"));
      cmd.Parameters.Add(new SqlParameter("@money", 100));
      cmd.ExecuteNonQuery();
    }
  }
}

public class WcfTest
{
  public static void Test()
  {
    // ---- Host -----

    AppDomain.CreateDomain("Server").DoCallBack(delegate
    {
      NetTcpBinding bindingServer = new NetTcpBinding();
      bindingServer.TransactionFlow = true;

      ServiceHost host1 = new ServiceHost(typeof(MyService1), new Uri("net.tcp://localhost:8080"));
      host1.AddServiceEndpoint(typeof(IService1), bindingServer, "");
      host1.Open();

      ServiceHost host2 = new ServiceHost(typeof(MyService2), new Uri("net.tcp://localhost:8081"));
      host2.AddServiceEndpoint(typeof(IService2), bindingServer, "");
      host2.Open();
    });

    // ---- Client -----

    NetTcpBinding bindingClient = new NetTcpBinding();
    bindingClient.TransactionFlow = true;

    IService1 client1 = ChannelFactory<IService1>.CreateChannel(bindingClient,
      new EndpointAddress("net.tcp://localhost:8080"));

    try
    {
      client1.Test();
    }
    finally
    {
      (client1 as IDisposable).Dispose();
    }
  }
}


运行结果表明,事务被正确提交。看来这和客户端使用 TransactionScope 必须显式调用 Complete() 有所不同。同样,如果将Service2.Test() 设为 TransactionAutoComplete=false,在不调用"OperationContext.Current.SetTransactionComplete();" 的情况下,也会触发事务失败异常。

原创粉丝点击