深入分析WCF事務投票實現(xiàn)方式
我們知道事務是通過參與方進行WCF事務投票(Voting)來決定 "提交(Complete)" 或者 "回滾(Rollback)" 操作的。默認情況下,WCF 通過 OperationBehavior(TransactionAutoComplete = true) 來完成投票動作。(TransactionAutoComplete = true 是缺省值,不需要顯式聲明。)
我們將服務方法默認的 TransactionAutoComplete=true 改為 false,看看結果 。
- // ---- Service1 -----
 - [ServiceContract(SessionModeSessionMode=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();
 - }
 - }
 - }
 - }
 
運行結果表明事務無法提交,觸發(fā) TransactionAbortedException 異常,顯示 "事務終止"。那么除了默認被稱之為 "聲明投票(Declarative voting)" 的方式外,我們還能怎么做?OperationContext 有個 SetTransactionComplete() 方法,允許我們在代碼中完成WCF事務投票行為。這種投票方式更加靈活,便于我們在代碼中做出更多的控制,被稱之為 "顯式投票(Explicit voting)"。
在上面兩個 Test() 方法的***一行,添加 "OperationContext.Current.SetTransactionComplete();",再次運行,事務被正確提交。
- [OperationBehavior(TransactionScopeRequired=true,
 
TransactionAutoComplete=false)]- public void Test()
 - {
 - // ...
 - OperationContext.Current.SetTransactionComplete();
 - }
 - ...
 
接下來,我們設想另外一種情況。事務不由 Client 發(fā)起,在 Service1.Test() 調用 Service2.Test(),那么事務會是個什么樣子呢?Service1、Service2 的參數(shù) "OperationBehavior(TransactionScopeRequired = true)" 決定了如果沒有外界傳入的環(huán)境事務,那么會自動創(chuàng)建一個根事務。所以 Service1.Test() 會創(chuàng)建一個根事務,而 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();" 的情況下,也會觸發(fā)事務失敗異常。
以上就是我們?yōu)榇蠹医榻B的WCF事務投票的相關實現(xiàn)方法。
【編輯推薦】















 
 
 






 
 
 
 