默認狀況下,服務類和操做沒有環境事務,即便客戶端事務傳播到服務端也是如此。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
下圖所展現的是服務使用的事務,這個事務是有綁定,操做契約屬性來分配的:設計
在上圖中,非事務客戶端調用服務1,服務1的操做契約配置爲TranscationFlowOption.Allowed(容許參與事務,若是調用方啓用了事務,則參與),儘管綁定容許事務流傳播,可是因爲客戶端沒有包含事務,因此沒法傳播事務。服務1的操做配置須要事務域。所以,WCF爲服務1建立了一個新的事務A。服務1隨後調用其它三個服務。服務2使用的綁定也是容許事務流操做,並且操做契約強制須要客戶端事務流。由於操做行爲配置須要配置事務流,WCF把事務A設置爲服務2的環境事務。服務3的綁定和操做契約不容許事務流傳播。可是,由於服務3的操做須要事務域,WCF爲此建立了一個新的事務B,並做爲服務3的環境事務。與服務3相似,服務4的綁定與操做也不容許事務流 傳播,可是服務4不須要事務域,因此WCF不會給它建立環境事務。3d
服務使用哪一個事務取決於綁定的事務流屬性(兩個值)、操做契約事務(三個值),以及操做行爲的事務域(兩個值)的設置。下表列出了可行的八種組合:日誌
八種組合實際產生四種有效的事務傳播模式,即客戶端/服務、客戶端、服務和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); } }運行結果:
結論:
從圖中咱們看到:
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); } }運行結果:
若是客戶端沒有啓用事務會產生異常信息
若是客戶端啓用事務,則客戶端會和服務端會使用同一事務流
這種事務模式,服務端不能獨立啓動事務,服務端事務必須參與客戶端事務流中,確保客戶端和服務端處於同一事務流中,這樣客戶端和服務端的代碼會作爲一個原子執行,當事務流中任何一個環節產生異常都會把整個事務流進行回滾,實現很是好的一致性。
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); }運行結果:
結論:
從圖中咱們能夠看到:
服務端本地事務ID不爲空,說明服務端代碼仍處於事務中執行。但全局事務ID都是空的,說明這種事務模型不存在全局型事務流。
還管客戶端事務執行是否成功,服務端事務老是成功執行。總結:
這種事務模型通常應用於:當服務端須要在客戶端事務流以外獨立運行事務的狀況。4.None事務模式
這種種事務模型中服務端代碼不啓用任何事務也不參與任何事務流。
實現步驟:
a.可使用任何綁定信道。若是選擇的是支持事務的綁定,須要設置TransactionFlow = false,由於服務端事務獨立啓動,並不須要事務流。
b.不須要在方法契約上添加TransactionFlowAttribute聲明。若是你非得設置此Attribute的話,請設置TransactionFlow(TransactionFlowOption.NotAllowed),指明服務端拒絕參與事務流。
c.不須要在方法行爲上添加TransactionScopeRequired屬性聲明,若是你非要設置此屬性的話,請設置OperationBehavior(TransactionScopeRequired=false)。指明此方法執行過程當中不使用事務。即默認狀況下,服務是不使用事務的。
None事務模式容許開發非事務型服務,它能夠被事務型客戶端調用。
顯然這種事務模型很危險,它破壞了系統的一致性:若是客戶端在執行過程當中產生異常時,將不會回滾服務端的數據。通常這在一些不會出現不一致性的系統中採用這種模型。
事務模型的選擇
上面所述的四種事務模型中,Service模型和None模型用得比較少,這兩種模式帶系統不一致的潛在危險。通常在須要事務流控制的地方,咱們應選擇Client/Service模型或Client模型來確保客戶端和服務端之間系統一致。
點擊下載