下面我們以一個例子來演示在wcf中使用分佈式事務。
爲了便於測試,我們在建立一個臨時表用於測試。表名爲MGender.表中有兩個字段:GenderCode,char(1),GenderDesc varchar(20).
在這個solution中WCFTrasactionServcies是contract 和Service。它是一個類庫項目,在這裏爲了方便我們將interfact和實現interfact寫在一個cs文件裏,當然這種方法是不被推薦的。代碼如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Data.SqlClient;
namespace WCFTrasactionServices
{
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IGenderInsert1
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
void insertGerder1(string genderID, string genderDesc);
}
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IGenderInsert2
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
void insertGerder2(string genderID, string genderDesc);
}
public class TestService1 : IGenderInsert1
{
#region IGenderInsert1 Members
[OperationBehavior(TransactionScopeRequired = true)]
public void insertGerder1(string genderID, string genderDesc)
{
using (SqlConnection conn = new SqlConnection("Data Source=localhost;DataBase=Test;integrated security=true"))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "insert into MGender (GenderCode,GenderDesc) values (@GenderCode,@GenderDesc)";
cmd.Parameters.Add(new SqlParameter("@GenderCode", genderID));
cmd.Parameters.Add(new SqlParameter("@GenderDesc", genderDesc));
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
}
#endregion
}
public class TestService2 : IGenderInsert2
{
#region IGenderInsert2 Members
[OperationBehavior(TransactionScopeRequired = true)]
public void insertGerder2(string genderID, string genderDesc)
{
using (SqlConnection conn = new SqlConnection("Data Source=localhost;DataBase=Test;integrated security=true"))
{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "insert into MGender (GenderCode,GenderDesc) values (@GenderCode,@GenderDesc)";
cmd.Parameters.Add(new SqlParameter("@GenderCode", genderID));
cmd.Parameters.Add(new SqlParameter("@GenderDesc", genderDesc));
cmd.Connection = conn;
cmd.ExecuteNonQuery();
}
}
#endregion
}
}
TransactionFlowAttribute 只能用於服務方法(Operation/Method)上,它允許我們進行不同的事務參與設置。有一點要注意,我們不能爲 IsOneWay=true 的服務設置事務支持。
TransactionFlowOption.NotAllowed: 不參與任何事務。(默認值)
TransactionFlowOption.Allowed: 允許參與事務。也就是說,如果調用方(客戶端)和服務Binding啓用了事務,則參與。
TransactionFlowOption.Mandatory: 強制啓用事務。調用方(客戶端)和服務 Binding 必須啓用事務才能調用本服務。
WCFTrasactionHost是host,爲了讓WCF支持分佈式事務,我們要修改Binding的一些屬性。只有 TCP-、 IPC- 以及 WS-related 等 Binding 支持事務。缺省情況下,這些 Binding 並不會參與事務,需要我們顯示將 TransactionFlow 屬性設置爲 true 才行。
我們可以通過配置文件來設置
<netTcpBinding>
<binding name = "Transactional" transactionFlow = "true" />
</netTcpBinding>
</bindings>
tcpBinding.TransactionFlow = true;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace WCFTrasactionHost
{
class Program
{
static void Main(string[] args)
{
ServiceHost host1 = new ServiceHost(typeof(WCFTrasactionServices.TestService1));
host1.Open();
ServiceHost host2 = new ServiceHost(typeof(WCFTrasactionServices.TestService2));
host2.Open();
Console.WriteLine("endpoint is listenning");
Console.ReadKey();
}
}
}
<configuration>
<system.serviceModel>
<services>
<service name ="WCFTrasactionServices.TestService1" behaviorConfiguration="TestBehavior1">
<endpoint binding="netTcpBinding" bindingConfiguration="TransactionalTCP" contract="WCFTrasactionServices.IGenderInsert1" address="net.tcp://localhost/Test1"></endpoint>
</service>
<service name ="WCFTrasactionServices.TestService2" behaviorConfiguration="TestBehavior2">
<endpoint binding="netTcpBinding" contract="WCFTrasactionServices.IGenderInsert2" bindingConfiguration="TransactionalTCP" address="net.tcp://localhost/Test2"></endpoint>
</service>
</services>
<bindings>
<netTcpBinding>
<binding name="TransactionalTCP" transactionFlow="true"></binding>
</netTcpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="TestBehavior1">
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost/Test1"/>
</behavior>
<behavior name="TestBehavior2">
<serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost/Test2"/>
</behavior>
</serviceBehaviors>
</system.serviceModel>
</configuration>
WCFTrasactionClient:是client.我們使用svcutil 工具生成客戶端代碼,生成兩個cs文件,TestService1.cs和TestService2.cs,當然我們也可以手工寫客戶端類。
WCFTrasactionClient代碼:
class Program
{
static void Main(string[] args)
{
GenderInsert1Client test1 = new GenderInsert1Client();
GenderInsert2Client test2 = new GenderInsert2Client();
using (TransactionScope scope = new TransactionScope())
{
try
{
test1.insertGerder1("M", "Male");
test2.insertGerder2("F"Female");
scope.Complete();
Console.WriteLine("Client has called the services.");
}
catch
{ //do noting
}
finally
{
//(test1 as IDisposable).Dispose();
//(test2 as IDisposable).Dispose();
}
}
}
這樣我們就可以成功的往表MGender中插入兩條數據。如果我們修改 :
test1.insertGerder1("M", "Male"); test2.insertGerder2("F"Female");修改爲 test1.insertGerder1("M", "Male"); test2.insertGerder2("FF"Female");
這樣就會有異常,因爲FF是兩位字符,而GenderCode是char1型的,所以事務不成功,但第一條數據也成功插入,所以WCF會自動回滾第一插入的數據 test1.insertGerder1("M", "Male");