WCF 支持分布式事务,也就是说事务可以跨越服务、进程、机器边界,在多个服务和客户端间存在.那么在WCF中如何使用分布式事务呢?
下面我们以一个例子来演示在wcf中使用分布式事务。下图是这个demo的整个solution:
为了便于测试,我们在建立一个临时表用于测试。表名为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>
</behaviors>
</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");