[WCF編程]12.事務:服務事務編程(上)

1、設置環境事務

    默認狀況下,服務類和操做沒有環境事務,即便客戶端事務傳播到服務端也是如此。tcp

    儘管強制事務流從客戶端傳播過來,但服務端的環境事務依舊爲null。爲了啓用環境事務,每一個操做必須告訴WCF啓用事務。爲了解決這個問題,WCF提供了OperationBehaviorAttribute的TransactionScopeRequired屬性:分佈式

// 指定服務方法的本地執行行爲。
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior
{
    //......
    // 獲取或設置一個值,該值指示方法在執行時是否須要事務環境。
    public bool TransactionScopeRequired { get; set; }
}

    TransactionScopeRequired的默認值爲false。這就是爲什麼默認狀況下服務沒有環境事務的緣由了。將TransactionScopeRequired設置爲true,將會爲操做啓用環境事務。函數

public class MyService : IMyService
{
    [OperationBehavior(TransactionScopeRequired=true)]
    public void MyMethod()
    {
        Transaction transaction = Transaction.Current;
        Debug.Assert(transaction != null);
    }
}

    若是客戶端事務傳播給服務,WCF會把客戶端事務做爲操做的環境事務。若是沒有傳播,WCF會爲操做建立一個新的事務,並把此事務視爲操做的環境事務。ui

    服務類構造函數沒有事務,他不能參與到客戶端事務裏,因此不能讓WCF將它加入到事務域中來。除非你手動建立環境事務,不然不要在服務構造函數裏執行事務代碼,構造函數中的代碼不會加入到客戶端事務。spa

    下圖所展現的是服務使用的事務,這個事務是有綁定,操做契約屬性來分配的:設計

image

    在上圖中,非事務客戶端調用服務1,服務1的操做契約配置爲TranscationFlowOption.Allowed(容許參與事務,若是調用方啓用了事務,則參與),儘管綁定容許事務流傳播,可是因爲客戶端沒有包含事務,因此沒法傳播事務。服務1的操做配置須要事務域。所以,WCF爲服務1建立了一個新的事務A。服務1隨後調用其它三個服務。服務2使用的綁定也是容許事務流操做,並且操做契約強制須要客戶端事務流。由於操做行爲配置須要配置事務流,WCF把事務A設置爲服務2的環境事務。服務3的綁定和操做契約不容許事務流傳播。可是,由於服務3的操做須要事務域,WCF爲此建立了一個新的事務B,並做爲服務3的環境事務。與服務3相似,服務4的綁定與操做也不容許事務流 傳播,可是服務4不須要事務域,因此WCF不會給它建立環境事務。3d

2、事務傳播模式

    服務使用哪一個事務取決於綁定的事務流屬性(兩個值)、操做契約事務(三個值),以及操做行爲的事務域(兩個值)的設置。下表列出了可行的八種組合:日誌

image

    八種組合實際產生四種有效的事務傳播模式,即客戶端/服務、客戶端、服務和None。上表也列出了每種模式推薦的配置。每種模式在應用程序中都有用武之地。code

1.客戶端/服務事務模式

    若是客戶端啓用了事務,則服務端就參與事務;若是客戶端沒有啓用事務,則服務端獨立啓用事務。即無論客戶端是否啓用事務,服務端老是運行在事務中。orm

實現步驟:

    a.選擇一個支持事務的綁定,設置TransactionFlow = true。
    b.在方法契約上添加TransactionFlowAttribute聲明,設置TransactionFlow(TransactionFlowOption.Allowed)。
    c.在方法行爲上聲明OperationBehavior(TransactionScopeRequired=true)。

    客戶端/服務事務模式是最解耦的配置,由於服務端儘可能不去考慮客戶端的工做。服務會在客戶端事務流傳播的時候容許加入客戶端事務。

服務代碼:

[ServiceContract]
    public interface IClientServiceTransaction
    {
        [OperationContract]
        //若是客戶端啓用了事務,則服務端參與事務
        [TransactionFlow(TransactionFlowOption.Allowed)]
        void CSMethod();
    }

    public class ClientServiceTransaction : IClientServiceTransaction
    {
        //服務端代碼必須置於服務中執行
        [OperationBehavior(TransactionScopeRequired = true)]
        public void CSMethod()
        {
            //獲取當前事務對象
            Transaction transaction = Transaction.Current;
            //註冊事務流執行結束的事件方法
            transaction.TransactionCompleted += new TransactionCompletedEventHandler(transaction_TransactionCompleted);
            //顯示事務的本地ID
            Debug.WriteLineIf(transaction != null, "<服務端>事務本地ID:" + transaction.TransactionInformation.LocalIdentifier);
        }
        //事務流執行結束時會觸發此方法
        void transaction_TransactionCompleted(object sender, TransactionEventArgs e)
        {
            //顯示事務全局的ID
            Debug.WriteLine("<服務端>事務全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier);
            //顯示事務的執行結果
            Debug.WriteLine("<服務端>事務執行結果" + e.Transaction.TransactionInformation.Status);
        }

    }

服務配置代碼:

<system.serviceModel>
    <services>
      <service name="WCF.Service.ClientServiceTransaction">
        <endpoint address="ClientServiceTransaction" contract="WCF.Service.IClientServiceTransaction" binding="netTcpBinding"/>
        <endpoint address="ClientServiceTransaction/mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://127.0.0.1:9000/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004">
          <reliableSession enabled="true"/>
        </binding>
      </netTcpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <diagnostics performanceCounters="All"/>
  </system.serviceModel>

客戶端代碼:

class Program
    {
        static void Main(string[] args)
        {
            ClientServiceTransactionClient prox = new ClientServiceTransactionClient("NetTcpBinding_IClientServiceTransaction");
            //第一次調用,客戶端沒有啓用事務
            prox.CSMethod();
            using (TransactionScope ts = new TransactionScope())
            {
                Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted);
                Console.WriteLine("<客戶端>事務本地ID:" + Transaction.Current.TransactionInformation.LocalIdentifier);
                //第二次調用事務,客戶端啓用了分佈式事務
                prox.CSMethod();

                //事務提交。若是提交,事務流會執行成功;若是不提交的話,事務流會回滾。
                //ts.Complete();
            }
            Console.ReadLine();
        }

        //事務流執行結束時會觸發此方法
        static void Current_TransactionCompleted(object sender, TransactionEventArgs e)
        {
            Console.WriteLine("<客戶端>事務全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier);
            Console.WriteLine("<客戶端>事務執行結果:" + e.Transaction.TransactionInformation.Status);
        }
    }

運行結果:

00961660354fc1f08cb10dda

結論:

從圖中咱們看到:
    1.服務端第一次執行成功,事務有本地ID,但沒有全局ID。這說明雖然客戶端調用沒有啓用事務,但服務端代碼仍在事務中運行,該事務是服務端本地事務。
    2.客戶的全局事務ID與服務端的全局事務ID相同,這說明客戶端與服務端處於同一個事務流中。但兩者本地的事務ID不一樣,這說明它們各自是處於全局「大事務」中的本地「小事務」。由此得出,服務端代碼仍在事務中運行,並參與到客戶端事務流中
    3.因爲客戶端代碼中ts.Complete()被註釋了,因此客戶端事務執行不會提交,從而致使服務端事務提交失敗,全局事務流提交失敗。

總結:

    Client/Service事務模型是最多見的,它對客戶端準備了「兩手應對策略」:
    當客戶端啓動了事務流的話,服務就參與事務流中,以維持整個系統的一致性,這樣客戶端的操做和服務端的操做會作爲一個總體進行操做,任何一處的操做產生異常都會致使整個事務流的回滾。
    若是客戶端沒有啓動事務流,服務端的操做仍須要在事務的保護下運行,它會自動啓動事務保護。

2.客戶端模式

    Client事務模型必須由客戶端啓動分佈式事務,並強制服務端必須參與客戶端事務流。

    當服務端必須使用客戶端事務且設計上不能單獨工做時,應該選用客戶端事務模式。使用這個模式的主要目的就是爲了最大化地保持一致性,由於客戶端與服務端的操做需做爲一個原子操做。還有一個重要緣由,即服務共享客戶端的事務能夠下降死鎖的風險,由於全部被訪問的資源都會加載到相同的事務裏。這意味着事務不可能再訪問相同的資源和事務鎖。

實現步驟:

    a.選擇一個支持事務的Binding,設置 TransactionFlow = true。
    b.在方法契約上添加TransactionFlowAttribute聲明,設置TransactionFlow(TransactionFlowOption.Mandatory)。
    c.在方法行爲上聲明OperationBehavior(TransactionScopeRequired=true)。

服務代碼:

/// <summary>
    /// 客戶端模式
    /// </summary>
    [ServiceContract]
    public interface IClientTransaction
    {
        [OperationContract]
        //強制把當前事務加入客戶端事務流中
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        void TestMethod();
    }
    public class ClientTransaction : IClientTransaction
    {
        //服務端代碼必須置於服務中執行
        [OperationBehavior(TransactionScopeRequired = true)]
        public void TestMethod()
        {
            Transaction transaction = Transaction.Current;
            transaction.TransactionCompleted += new TransactionCompletedEventHandler(transaction_TransactionCompleted);
            Debug.WriteLineIf(transaction != null, "<服務端>事務本地ID:" + transaction.TransactionInformation.LocalIdentifier);
        }
        void transaction_TransactionCompleted(object sender, TransactionEventArgs e)
        {
            Debug.WriteLine("<服務端>事務全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier);
            Debug.WriteLine("<服務端>事務執行結果:" + e.Transaction.TransactionInformation.Status);
        }

    }

服務配置代碼:

<system.serviceModel>
    <services>
      <service name="WCF.ServiceForClient.ClientTransaction">
        <endpoint address="ClientTransaction" contract="WCF.ServiceForClient.IClientTransaction" binding="netTcpBinding"/>
        <endpoint address="ClientTransaction/mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://127.0.0.1:9000/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004">
          <reliableSession enabled="true"/>
        </binding>
      </netTcpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <diagnostics performanceCounters="All"/>
  </system.serviceModel>
</configuration>

客戶端代碼:

/// <summary>
    /// 客戶端事務
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //客戶端不啓用事務,會產生異常
            //ClientTransactionClient prox = new ClientTransactionClient();
            //prox.Open();
            //prox.TestMethod();
            //prox.Close();

            //------------------------------------------

            //客戶端啓用事務
            ClientTransactionClient prox = new ClientTransactionClient();
            prox.Open();
            using (TransactionScope ts = new TransactionScope())
            {
                prox.TestMethod();
                //獲取當前事務對象
                Transaction trans = Transaction.Current;
                //註冊服務結束事件方法
                trans.TransactionCompleted += new TransactionCompletedEventHandler(trans_TransactionCompleted);
                //顯示事務本地ID;
                Console.WriteLine("<客戶端>事務本地ID:" + trans.TransactionInformation.LocalIdentifier);
                //提交事務
                ts.Complete();
            }
            prox.Close();


            Console.ReadLine();
        }
        //事務結束觸發的事件
        static void trans_TransactionCompleted(object sender, TransactionEventArgs e)
        {
            Console.WriteLine("<客戶端>事務全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier);
            Console.WriteLine("<客戶端>事務執行結果:" + e.Transaction.TransactionInformation.Status);
        }
    }

運行結果:

    若是客戶端沒有啓用事務會產生異常信息

image

    若是客戶端啓用事務,則客戶端會和服務端會使用同一事務流

a9697cd36ad7292f3bf3cfa2

    這種事務模式,服務端不能獨立啓動事務,服務端事務必須參與客戶端事務流中,確保客戶端和服務端處於同一事務流中,這樣客戶端和服務端的代碼會作爲一個原子執行,當事務流中任何一個環節產生異常都會把整個事務流進行回滾,實現很是好的一致性。

3.服務事務模型

    這種事務模型是把服務端事務與客戶端事務分離開,服務端代碼執行老是會建立本身的事務,並不會參與到客戶端事務中去。因此客戶端的事務啓用與否並不影響服務端事務的執行。

實現步驟:

    a.可使用任何綁定信道。若是選擇的是支持事務的綁定,須要設置TransactionFlow = false,由於服務端事務獨立啓動,並不須要事務流。
    b.不須要在方法契約上添加TransactionFlowAttribute聲明。若是你非得設置此Attribute的話,請設置TransactionFlow(TransactionFlowOption.NotAllowed),指明服務端拒絕參與事務流。
    c.在方法行爲上聲明OperationBehavior(TransactionScopeRequired=true)。指明服務端須要本身啓用事務。

    當服務端工做不須要在客戶端事務範圍內完成時,能夠選擇服務端事務模式(連例如,日誌或審計操做,或者不論客戶端事務提交成功或終止,都像將事件給服務端)。

    例如,咱們編寫一個記錄操做日誌的服務,客戶端所作的每一步操做都須要調用服務端的方法進行記錄。這個日誌記錄服務端就適用於Service服務模型,無論客戶端事務是否正常提交,服務端記錄日誌的事務不受影響。若是採起的是Client/Service事務模型或Client事務模型的話,當客戶端事務沒有提交成功,會致使服務端日誌沒法正常記錄。

服務代碼:

/// <summary>
    /// 服務端事務模式
    /// </summary>
    [ServiceContract]
    public interface IServiceTransaction
    {
        [OperationContract]
        //禁用事務流功能
        [TransactionFlow(TransactionFlowOption.NotAllowed)]
        void TestMethod();
    }
    public class ServiceTransaction : IServiceTransaction
    {
        //服務端代碼必須置於服務中執行
        [OperationBehavior(TransactionScopeRequired = true)]
        public void TestMethod()
        {
            Transaction transaction = Transaction.Current;
            transaction.TransactionCompleted += new TransactionCompletedEventHandler(transaction_TransactionCompleted);
            Debug.WriteLineIf(transaction != null, "<服務端>事務本地ID" + transaction.TransactionInformation.LocalIdentifier);
        }
        void transaction_TransactionCompleted(object sender, TransactionEventArgs e)
        {
            Debug.WriteLine("<服務端>事務全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier);
            Debug.WriteLine("<服務端>事務執行結果:" + e.Transaction.TransactionInformation.Status);
        }
    }

服務配置代碼:

<system.serviceModel>
    <services>
      <service name="WCF.ServiceForService.ServiceTransaction">
        <endpoint address="ServiceTransaction" contract="WCF.ServiceForService.IServiceTransaction" binding="netTcpBinding"/>
        <endpoint address="ServiceTransaction/mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://127.0.0.1:9000/"/>
          </baseAddresses>
        </host>
      </service>
    </services>
    <bindings>
      <netTcpBinding>
        <binding transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004">
          <reliableSession enabled="true"/>
        </binding>
      </netTcpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <diagnostics performanceCounters="All"/>
  </system.serviceModel>

客戶端代碼:

public static void Main(string[] args)
        {
            ServiceTransactionClient prox = new ServiceTransactionClient();
            prox.Open();
            using (TransactionScope ts = new TransactionScope())
            {
                prox.TestMethod();
                Transaction trans = Transaction.Current;
                trans.TransactionCompleted += new TransactionCompletedEventHandler(trans_TransactionCompleted);
                Console.WriteLine("<客戶端>事務本地ID:" + trans.TransactionInformation.LocalIdentifier);
                //ts.Complete();
            }
            prox.Close();
            Console.ReadLine();
        }
        static void trans_TransactionCompleted(object sender, TransactionEventArgs e)
        {
            Console.WriteLine("<客戶端>事務全局ID:" + e.Transaction.TransactionInformation.DistributedIdentifier);
            Console.WriteLine("<客戶端>事務執行結果:" + e.Transaction.TransactionInformation.Status);
        }

運行結果:

1193138266464f8d0df4d2ad

結論: 

    從圖中咱們能夠看到:
     服務端本地事務ID不爲空,說明服務端代碼仍處於事務中執行。但全局事務ID都是空的,說明這種事務模型不存在全局型事務流。
     還管客戶端事務執行是否成功,服務端事務老是成功執行。

總結:
    這種事務模型通常應用於:當服務端須要在客戶端事務流以外獨立運行事務的狀況。

4.None事務模式

    這種種事務模型中服務端代碼不啓用任何事務也不參與任何事務流。

實現步驟:

    a.可使用任何綁定信道。若是選擇的是支持事務的綁定,須要設置TransactionFlow = false,由於服務端事務獨立啓動,並不須要事務流。
    b.不須要在方法契約上添加TransactionFlowAttribute聲明。若是你非得設置此Attribute的話,請設置TransactionFlow(TransactionFlowOption.NotAllowed),指明服務端拒絕參與事務流。
    c.不須要在方法行爲上添加TransactionScopeRequired屬性聲明,若是你非要設置此屬性的話,請設置OperationBehavior(TransactionScopeRequired=false)。指明此方法執行過程當中不使用事務。

    即默認狀況下,服務是不使用事務的。

    None事務模式容許開發非事務型服務,它能夠被事務型客戶端調用。

    顯然這種事務模型很危險,它破壞了系統的一致性:若是客戶端在執行過程當中產生異常時,將不會回滾服務端的數據。通常這在一些不會出現不一致性的系統中採用這種模型。

事務模型的選擇

    上面所述的四種事務模型中,Service模型和None模型用得比較少,這兩種模式帶系統不一致的潛在危險。通常在須要事務流控制的地方,咱們應選擇Client/Service模型或Client模型來確保客戶端和服務端之間系統一致。

3、實例代碼下載

image

點擊下載

相關文章
相關標籤/搜索