WCF技術剖析之三十一: WCF事務編程[下篇]
在WCF事務編程模型下,通過服務契約確定事務流轉的策略(參閱《上篇》),通過事務綁定實施事務的流轉(參閱《中篇》)。但是,對於事務綁定接收到並成功創建的事務來說,服務操作的執行是否需要自動登記到該事務之中,以及服務操作采用怎樣的提交方式,這就是服務端自己說了算了。正因為如此,WCF通過服務(操作)行為的形式定義事務的登記和提交(完成)方式。
一、事務的自動登記(Enlistment)與提交(完成)
在OperationBehaviorAttribute特性(其本身是一個操作行為)中定了兩個與事務管理相關的屬性:TransactionAutoComplete和TransactionScopeRequired。
1: [AttributeUsage(AttributeTargets.Method)]
2: public sealed class OperationBehaviorAttribute : Attribute, IOperationBehavior
3: {
4: //其他成員
5: public bool TransactionScopeRequired { get; set; }
6: public bool TransactionAutoComplete { get; set; }
7: }
如上麵的代碼所示,這兩個屬性均為布爾類型,它們代表的含義如下:
- TransactionScopeRequired:表示相應的操作的整個執行是否自動登記到一個事務中。具體來講,如果客戶端流程成功地流入服務端,並被服務端事務綁定成功接收,將該屬性設為True以為著整個操作的執行將自動被納入到流入的事務之中,服務操作將會成為客戶端事務的一部分。如果服務端不曾成功接收流入的事務,將該屬性設為True意味著操作的執行將會被納入到一個新創建的事務中。TransactionScopeRequired的默認值為False;
- TransactionAutoComplete:表示如果操作執行過程中沒有拋出異常,完成後將自動提交事務(對於最外層的事務)或者向被依賴的事情進行投票(對於嵌套的依賴事務)。TransactionAutoComplete的默認值為True。
如果我們需要將整個操作(而不是操作的一部分)納入到事務中執行,我們隻需要將OperationBehaviorAttribute特性應用到服務類型中的相應的方法之上,並將TransactionScopeRequired屬性設為True即可。相麵的代碼中,我通過應用OperationBehaviorAttribute特性將服務BankingService的Transfer方法定義成事務型操作方法。
1: public class BankingService : IBankingService
2: {
3: [OperationBehavior(TransactionScopeRequired = true)]
4: public void Transfer(string accountFrom, string accountTo, double amount)
5: {
6: //省略實現
7: }
8: }
將TransactionAutoComplete屬性設為True(默認就是True)可以使我們需要考慮對本地事務的提交問題。隻要執行完最後一句代碼尚無異常拋出,則會提交(或完成)事務。但是有時候,我們需要不同的事務提交(完成)策略,比如服務方法中包含一些非事務型操作(比如日記記錄),隻要保證正常的業務邏輯正常執行就可以提交(完成)事務。在這種情況下,我們可以將該屬性設為False,通過調用當前OperationContext的SetTransactionComplete方法即可實現對本地事務的提交。
1: public sealed class OperationContext : IExtensibleObject<OperationContext>
2: {
3: //其他成員
4: public void SetTransactionComplete();
5: }
除了定義在OperationBehaviorAttribute特性中的基於操作的行為,還有一些與事務相關的服務行為,它們定義在我們熟悉的ServiceBehaviorAttribute特性中。
二、事務相關的服務行為
如下麵的代碼所示, ServiceBehaviorAttribute特性定義了四個與事務相關的屬性。其中TransactionIsolationLevel指定事務的隔離級別,默認值為IsolationLevel.Serializable;TransactionTimeout以字符串定義事務的超市時限,WCF運行時會根據指定的字符串創建TimeSpan對象;TransactionAutoCompleteOnSessionClose表示在會話正常結束(沒有出現異常)之後是否自動提交或為完成開啟的事務,默認值為False;ReleaseServiceInstanceOnTransactionComplete則表示當事務完成之後是否需要將服務實例釋放掉,默認為False。
1: [AttributeUsage(AttributeTargets.Class)]
2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
3: {
4: //其他成員
5: public IsolationLevel TransactionIsolationLevel { get; set; }
6: public string TransactionTimeout { get; set; }
7: public bool TransactionAutoCompleteOnSessionClose { get; set; }
8: public bool ReleaseServiceInstanceOnTransactionComplete { get; set; }
9: }
在下麵的代碼中,通過在BankingService上應用ServiceBehaviorAttribute特性將隔離級別設置成ReadCommitted,超時時限設置成5分鍾,並將TransactionAutoCompleteOnSessionClose設置成True。
1: [ServiceBehavior(TransactionIsolationLevel = IsolationLevel.ReadCommitted,
2: TransactionTimeout = "00:05:00",
3: TransactionAutoCompleteOnSessionClose = true)]
4: public class BankingService : IBankingService
5: {
6: //省略成員
7: }
當你通過ServiceBehaviorAttribute特性對上述的四個屬性的任一個進行的設置,即使是設置成默認值,如果服務中並不存在一個TransactionScopeRequired屬性為True的操作,在進行服務寄宿的時候將會拋出異常。就以上麵的設置為例,在BankingService中的唯一的Transfer方法上,並沒有通過OperationBehaviorAttribute將TransactionScopeRequired屬性為True,在對服務進行寄宿的時候,就會拋出如圖1所示的InvalidOperationException異常。
1: [ServiceBehavior(TransactionIsolationLevel = IsolationLevel.ReadCommitted,
2: TransactionTimeout = "00:05:00",
3: TransactionAutoCompleteOnSessionClose = true)]
4: public class BankingService : IBankingService
5: {
6: public void Transfer(string accountFrom, string accountTo, double amount)
7: {
8: //省略實現
9: }
10: }
圖1 為不存TransactionScopeRequired操作的服設置在事務相關服務行為導致的異常
通過TransactionTimeout設置的事務超時時限最終會被賦予ChannelDispatcher的同名屬性。該屬性的默認值為TimeSpan.Zero,在這種情況下,運行時將會采用System.Transactions的默認超時設置。如果設置的TransactionTimeout的值超過了System.Transactions設置的最大超時時限,後者將會自動作為運行時的事務超時時限。相關類型請參考《談談分布式事務之三: System.Transactions事務詳解[上篇]》和《談談分布式事務之三: System.Transactions事務詳解[下篇]》
1: public class ChannelDispatcher : ChannelDispatcherBase
2: {
3: //其他成員
4: public TimeSpan TransactionTimeout { get; set; }
5: }
基於事務超時時限的服務行為也可以通過配置的方式指定,對應的配置節為<serviceTimeouts>,你隻需按照所需的各式設置transactionTimeout屬性值即可。在下麵的配置中,我們將BankingService服務的事務超時時限設置成30分鍾。
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service behaviorConfiguration="transactionBehavior" name="Artech.TransactionalServices.BankingService">
6: <endpoint address="net.tcp://127.0.0.1:3721/bankingservice" binding="netTcpBinding" contract="Artech.TransactionalServices.IBankingService" />
7: </service>
8: </services>
9: <behaviors>
10: <serviceBehaviors>
11: <behavior name="transactionBehavior">
12: <serviceTimeouts transactionTimeout="00:30:00" />
13: </behavior>
14: </serviceBehaviors>
15: </behaviors>
16: </system.serviceModel>
17: </configuration>
在事務流轉的場景中,流入的事務和目標服務的事務隔離級別必須一致。也就是說,如果服務調用存在於客戶端現有的事務之中,當前客戶端事務的隔離級別必須和目標服務具有相同的隔離級別。否則,服務端將會返回相應的Fualt消息並導致客戶端拋出異常。同樣對於上麵我們定義的BankingService(TransactionIsolationLevel = IsolationLevel.ReadCommitted),如果客戶端按照下麵的方式進行調用,由於客戶端事務采用默認的隔離界別Serializable,會拋出如圖2所示的ProtocolException異常。
1: using (ChannelFactory<IBankingService> channelFactory = new ChannelFactory<IBankingService>("bankingservice"))
2: {
3: IBankingService bankingservice = channelFactory.CreateChannel();
4: using (TransactionScope transactionScope = new TransactionScope())
5: {
6: bankingservice.Transfer("Foo", "Bar", 1000);
7: transactionScope.Complete();
8: }
9: }
到此為止,WCF事務編程模型涉及到的三個方麵,即服務(操作)契約、綁定和服務(操作)行為就介紹完了。接下來,我們將給出一個完整的例子。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 16:04:31