介紹
WCF(Windows Communication Foundation) - 事務(Transaction):
·對契約方法使用TransactionFlowAttribute聲明(設置TransactionFlowOption參數),以指定服務操作的事務流策略
·對服務方法是用OperationBehaviorAttribute聲明(設置TransactionScopeRequired參數),以指定方法是否在事務范圍(TransactionScope)內執行
·配置host和client的binding節點的transactionFlow屬性,以指定綁定是否支持流事務
示例
1、服務
Hello.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCF.ServiceLib.Transaction
{
/**//// <summary>
/// IHello接口
/// </summary>
[ServiceContract]
public interface IHello
{
/**//// <summary>
/// 打招呼方法
/// </summary>
/// <param name="name">人名</param>
/// <remarks>
/// TransactionFlow - 指定服務操作是否願意接受來自客戶端的傳入事務
/// NotAllowed - 禁止事務。默認值
/// Allowed - 允許事務
/// Mandatory - 強制事務
/// </remarks>
/// <returns></returns>
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
void WriteHello(string name);
}
/**//// <summary>
/// Hello類
/// </summary>
public class Hello : IHello
{
/**//// <summary>
/// 打招呼方法
/// </summary>
/// <param name="name">人名</param>
/// <remarks>
/// OperationBehavior - 指定服務方法的本地執行行為
/// 1、TransactionScopeRequired - 如果方法需要事務范圍才能執行,則為 true;否則為 false。默認值為 false
/// 將 TransactionScopeRequired 設置為 true,可以要求操作在事務范圍內執行。如果流事務可用,則操作會在該事務內執行。如果流事務不可用,則會創建一個新事務並使用它來執行操作
/// 2、TransactionAutoComplete - 默認值為 true
/// true - 當方法完成執行時,將把該事務標志為完成(自動提交事務)
/// false - 需要調用OperationContext.Current.SetTransactionComplete()方法來手工配置該事務的正確完成;否則,該事務將被標志為失敗(手動提交事務)
/// </remarks>
/// <returns></returns>
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void WriteHello(string name)
{
DBDataContext ctx = new DBDataContext();
ctx.Items.InsertOnSubmit(
new Item
{
Title = string.Format("Hello: {0}, TransactionId: {1}", name, System.Transactions.Transaction.Current.TransactionInformation.LocalIdentifier),
CreatedTime = DateTime.Now
});
ctx.SubmitChanges();
}
}
}
Hi.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCF.ServiceLib.Transaction
{
/**//// <summary>
/// IHi接口
/// </summary>
[ServiceContract]
public interface IHi
{
/**//// <summary>
/// 打招呼方法
/// </summary>
/// <param name="name">人名</param>
/// <returns></returns>
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
void WriteHi(string name);
}
/**//// <summary>
/// Hi類
/// </summary>
public class Hi : IHi
{
/**//// <summary>
/// 打招呼方法
/// </summary>
/// <param name="name">人名</param>
/// <returns></returns>
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void WriteHi(string name)
{
if (DateTime.Now.Second % 2 == 0)
throw new System.Exception("為測試事務而拋出的異常");
DBDataContext ctx = new DBDataContext();
ctx.Items.InsertOnSubmit(
new Item
{
Title = string.Format("Hi: {0}, TransactionId: {1}", name, System.Transactions.Transaction.Current.TransactionInformation.LocalIdentifier),
CreatedTime = DateTime.Now
});
ctx.SubmitChanges();
}
}
}
Result.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCF.ServiceLib.Transaction
{
/**//// <summary>
/// 結果接口
/// </summary>
[ServiceContract]
public interface IResult
{
[OperationContract]
List<Item> GetResult();
}
/**//// <summary>
/// 結果類
/// </summary>
public class Result : IResult
{
/**//// <summary>
/// 返回數據庫結果
/// </summary>
/// <returns></returns>
public List<Item> GetResult()
{
DBDataContext ctx = new DBDataContext();
var result = from l in ctx.Items
orderby l.CreatedTime descending
select l;
return result.ToList();
}
}
}
2、宿主
Hello.svc
<%@ ServiceHost Language="C#" Debug="true" Service="WCF.ServiceLib.Transaction.Hello" %>
Hi.svc
<%@ ServiceHost Language="C#" Debug="true" Service="WCF.ServiceLib.Transaction.Hi" %>
Result.svc
<%@ ServiceHost Language="C#" Debug="true" Service="WCF.ServiceLib.Transaction.Result" %>
Web.config
<?xml version="1.0"?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="TransactionBehavior"> <!--httpGetEnabled - 指示是否發布服務元數據以便使用 HTTP/GET 請求進行檢索,如果發布 WSDL,則為 true,否則為 false,默認值為 false--> <serviceMetadata httpGetEnabled="true" /> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <services> <!--name - 提供服務的類名--> <!--behaviorConfiguration - 指定相關的行為配置--> <service name="WCF.ServiceLib.Transaction.Hello" behaviorConfiguration="TransactionBehavior"> <!--address - 服務地址--> <!--binding - 通信方式--> <!--contract - 服務契約--> <!--bindingConfiguration - 指定相關的綁定配置--> <endpoint address="" binding="wsHttpBinding" contract="WCF.ServiceLib.Transaction.IHello" bindingConfiguration="TransactionConfiguration" /> </service> <service name="WCF.ServiceLib.Transaction.Hi" behaviorConfiguration="TransactionBehavior"> <endpoint address="" binding="wsHttpBinding" contract="WCF.ServiceLib.Transaction.IHi" bindingConfiguration="TransactionConfiguration" /> </service> <service name="WCF.ServiceLib.Transaction.Result" behaviorConfiguration="TransactionBehavior"> <endpoint address="" binding="basicHttpBinding" contract="WCF.ServiceLib.Transaction.IResult" /> </service> </services> <bindings> <wsHttpBinding> <!--transactionFlow - 指定該綁定是否應支持流事務--> <binding name="TransactionConfiguration" transactionFlow="true" /> </wsHttpBinding> </bindings> </system.serviceModel> </configuration>
3、客戶端
Sample.aspx
<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeFile="Sample.aspx.cs" Inherits="Transaction_Sample" Title="事務(Transaction)" %> <asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server"> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server"> <p> <asp:Label ID="lblErr" runat="server" ForeColor="Red" /> </p> <p> <asp:Button ID="btnSubmit" runat="server" Text="事務測試" OnClick="btnSubmit_Click" /> <br /> <br /> <asp:GridView ID="GridView1" runat="server"> </asp:GridView> </p> <p> 2PC(Two Phase Commitment Protocol)兩階段提交協議(WCF的事務的實現基於此協議) <br /> 實現分布式事務的關鍵就是兩階段提交協議。在此協議中,一個或多個資源管理器的活動均由一個稱為事務協調器的單獨軟件組件來控制。此協議中的五個步驟如下: <br /> 1、應用程序調用事務協調器中的提交方法。 <br /> 2、事務協調器將聯絡事務中涉及的每個資源管理器,並通知它們准備提交事務(這是第一階段的開始)。 <br /> 3、為 了以肯定的方式響應准備階段,資源管理器必須將自己置於以下狀態:確保能在被要求提交事務時提交事務,或在被要求回滾事務時回滾事務。大多數資源管理器會將包含其計劃更改的日記文件(或等效文件)寫入持久存儲區中。如果資源管理器無法准備事務,它會以一個否定響應來回應事務協調器。 <br /> 4、事務協調器收集來自資源管理器的所有響應。 <br /> 5、在 第二階段,事務協調器將事務的結果通知給每個資源管理器。如果任一資源管理器做出否定響應,則事務協調器會將一個回滾命令發送給事務中涉及的所有資源管理 器。如果資源管理器都做出肯定響應,則事務協調器會指示所有的資源管理器提交事務。一旦通知資源管理器提交,此後的事務就不能失敗了。通過以肯定的方式響應第一階段,每個資源管理器均已確保,如果以後通知它提交事務,則事務不會失敗。 </p> </asp:Content>
Sample.aspx.cs
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Threading;
public partial class Transaction_Sample : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnSubmit_Click(object sender, EventArgs e)
{
var proxyHello = new TransactionSvc.Hello.HelloClient();
var proxyHi = new TransactionSvc.Hi.HiClient();
var proxyResult = new TransactionSvc.Result.ResultClient();
System.Transactions.TransactionOptions to = new System.Transactions.TransactionOptions();
// 設置事務的超時時間
to.Timeout = new TimeSpan(0, 0, 30);
// 設置事務的隔離級別
to.IsolationLevel = System.Transactions.IsolationLevel.Serializable;
using (var ts = new System.Transactions.TransactionScope())
{
try
{
proxyHello.WriteHello("webabcd");
proxyHello.Close();
proxyHi.WriteHi("webabcd");
proxyHi.Close();
ts.Complete();
lblErr.Text = "OK";
}
catch (Exception ex)
{
lblErr.Text = ex.ToString();
}
}
GridView1.DataSource = proxyResult.GetResult();
GridView1.DataBind();
proxyHello.Close();
}
}
Web.config
<?xml version="1.0"?> <configuration> <system.serviceModel> <client> <!--address - 服務地址--> <!--binding - 通信方式--> <!--contract - 服務契約--> <endpoint address="http://localhost:3502/ServiceHost/Transaction/Hello.svc" binding="wsHttpBinding" contract="TransactionSvc.Hello.IHello" bindingConfiguration="TransactionBindingConfiguration" /> <endpoint address="http://localhost:3502/ServiceHost/Transaction/Hi.svc" binding="wsHttpBinding" contract="TransactionSvc.Hi.IHi" bindingConfiguration="TransactionBindingConfiguration" /> <endpoint address="http://localhost:3502/ServiceHost/Transaction/Result.svc" binding="basicHttpBinding" contract="TransactionSvc.Result.IResult" /> </client> <bindings> <wsHttpBinding> <!--transactionFlow - 指定該綁定是否應支持流事務--> <binding name="TransactionBindingConfiguration" transactionFlow="true" /> </wsHttpBinding> </bindings> </system.serviceModel> </configuration>
運行結果:
單擊"btnSubmit"按鈕後,可以發現,兩個數據庫插入操作,要麼都執行,要麼都不執行
OK