1. WCF服务中事务的边界
WCF服务中,事务是以方法为边界的,每个WCF服务的方法可以有独立事务的执行模式。而事务可以在多个服务中传播,也可以在服务端与客户端之间传播,介时事务管理器的级数将会晋升。
2. 简单的事务使用方式
TransactionScopeRequired与TransactionAutoComplete是WCF事务的基本元素。
当TransactionScopeRequired等于true时,代表在此WCF服务的方法中启动事务。反之,当此值为false时代表此方法不执行事务。
当TransactionAutoComplete等于true时,代表该方法使用隐式事务,这也是微软推荐使用的方法。即当该方法在运行过程中没有抛出Exception,操作就默认为完成,事务将自动提交。如果期间出现任何异常,事务就会自动回滚。如果TransactionAutoComplete等于false时,该方法即为显式事务,即需要在方法完成时利用OperationContext.Current.SetTransactionComplete () 显式提交事务。
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IService
{
[OperationContract]
void Method1();
[OperationContract]
void Method2();
}
public class Service : IService
{
//隐式事务
[OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
public void Method1()
{
...........
}
//显式事务
[OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=false)]
public void Method2()
{
...........
OperationContext.Current.SetTransactionComplete();
}
}
在同一个应用程序域中,事务默认能相互传播,在上面的例子当中,当方法Method1()直接调用Mehtod2()的时候,事务也能够成功流转。
事务也能够在服务端与客户端之间传播,还能跨越服务边界,在多个系统当中流转,在WCF里把服务中的事务传播称作事务流(Transaction Flow)。如果事务流需要在服务端和客户端成功传播或使用分布式事务,必须具备以下条件:
- 绑定必须支持事务,在WCF内并非所有绑定都支持事务,像常用的BasicHttpBinding就不支持事务的传播。只有以下几个绑定才能支持事务流的运转:NetTcpBinding、WSHttpBinding、WSDualHttpBinding、WSFederationHttpBinding、NetNamedPipeBinding。
- 服务方法必须设置好TransactionScopeRequired与TransactionAutoComplete两个事务的基本元素,要成功启动事务,这是基础条件。
- 把TransactionFlow设置为true,这代表启动事务流,允许在SOAP头部放置事务的信息。一般情况下TransactionFlow的默认值为false ,这表示事务只能在服务器的同一应用程序域内流转,而不能实现服务端与客户端之间的传播。
- 把服务契约的TransactionFlowOption设置为Allowed,这代表允许客户端的事务传播到服务端。
- 客户端必须启动一个事务,在最后使用TransactionScope.Complete ( ) 提交事务。
TransactionFlowOption说明
TransactionFlowOption有三个选项:
一为NotAllowed,这代表了禁止客户端传播事务流到服务端,即使客户端启动了事务,该事务也会被忽略;
二为Allowed,这代表允许客户端的事务传播到服务端,但服务器端不一定会引用到此事务;
三为Mandatory,这代表服务端与客户端必须同时启动事务流,否则就会抛出InvalidOperationException异常。
下面举几个例子来讲解一下事务流的使用方式。
3.1 在服务端与客户端之间传播事务 这是事务流的基本使用方式,首先在服务端使用wsHttpBinding绑定建立一个服务契约,在方法中利用TransactionInformation对象检测一下事务的状态。然后设置好TransactionScopeRequired与TransactionAutoComplete属性来启动事务,在*.config中把TransactionFlow设置为true。再把服务的TransactionFlowOption设置为Allowed,最后在客户端通过TransactionScope.Complete()的方法提交事务。
服务端:
namespace Example
{
[ServiceContract]
public interface IExampleService
{
[OperationContract]
void Method1();
}
public class ExampleService : IExampleService
{
//使用隐式事务,并把TransactionFlowOption设置为Allowed打开事务流
[OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)]
[TransactionFlow(TransactionFlowOption.Allowed)]
public void Method1()
{
//通过TransactionInformation检测事务状态
Transaction transaction = Transaction.Current;
string info=string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
transaction.TransactionInformation.DistributedIdentifier,
transaction.TransactionInformation.LocalIdentifier);
Console.WriteLine("Method1: \n"+info);
}
static void Main(string[] args)
{
//启动服务
ServiceHost exampleHost = new ServiceHost(typeof(Example.ExampleService));
exampleHost.Open();
Console.WriteLine("service start");
Console.ReadKey();
Console.WriteLine("service end");
exampleHost.Close();
}
}
}
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<!--启动事务流-->
<binding name="defaultWSHttpBinding" transactionFlow="true" />
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="Example.ExampleService">
<!--使用支持事务流的wsHttpBinding绑定-->
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="defaultWSHttpBinding"
contract="Example.IExampleService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:7200/Example/ExampleService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
客户端:
class Program
{
static void Main(string[] args)
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
ShowTransactionMessage("start");
ExampleServiceReference.ExampleServiceClient exampleService1 = new
ExampleServiceReference.ExampleServiceClient();
exampleService1.Method1();
ShowTransactionMessage("exampleService started");
exampleService1.Close();
//事务提交
scope.Complete();
}
Console.ReadKey();
}
//检查事务的状态
static void ShowTransactionMessage(string data)
{
if (Transaction.Current != null)
{
Transaction transaction = Transaction.Current;
string info = string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
transaction.TransactionInformation.DistributedIdentifier,
transaction.TransactionInformation.LocalIdentifier);
Console.WriteLine(data+" \n" + info);
}
}
}
在分布式系统当中,单个客户端可能引用多个服务,分布式事务能协调多方的操作。多个系统中的操作要不同时成功,要不同时失败。下面的例子中,客户端同时引用了ExampleService服务和ExtensionService服务,并启动了分布式事务。而在客户端与两个服务端的事务都是通过DistributedIndentifier 作为事务的标识的。
服务端:
//*******************************ExampleService**********************************//
namespace Example
{
[ServiceContract]
public interface IExampleService
{
[OperationContract]
void Method1();
}
public class ExampleService : IExampleService
{
//使用隐式事务,并把TransactionFlowOption设置为Allowed
[OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)]
[TransactionFlow(TransactionFlowOption.Allowed)]
public void Method1()
{
//通过TransactionInformation检测事务状态
Transaction transaction = Transaction.Current;
string info=string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
transaction.TransactionInformation.DistributedIdentifier,
transaction.TransactionInformation.LocalIdentifier);
Console.WriteLine("Method1: \n"+info);
}
static void Main(string[] args)
{
//启动服务
ServiceHost exampleHost = new ServiceHost(typeof(Example.ExampleService));
exampleHost.Open();
Console.WriteLine("service start");
Console.ReadKey();
Console.WriteLine("service end");
exampleHost.Close();
}
}
}
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<!--启动事务流-->
<binding name="defaultWSHttpBinding" transactionFlow="true" />
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="Example.ExampleService">
<!--使用支持事务流的wsHttpBinding绑定-->
<endpoint address="" binding="wsHttpBinding" contract="Example.IExampleService"
bindingConfiguration="defaultWSHttpBinding">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:7200/Example/ExampleService/" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
//*************************************Extension********************************//
namespace Extension
{
[ServiceContract]
public interface IExtensionService
{
[OperationContract]
void DoWork();
}
public class ExtensionService : IExtensionService
{
[OperationBehavior(TransactionAutoComplete=true,TransactionScopeRequired=true)]
[TransactionFlow(TransactionFlowOption.Allowed)]
public void DoWork()
{
Transaction transaction = Transaction.Current;
string info = string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
transaction.TransactionInformation.DistributedIdentifier,
transaction.TransactionInformation.LocalIdentifier);
Console.WriteLine("DoWork: \n" + info);
}
static void Main(string[] args)
{
Console.WriteLine("extension service start");
ServiceHost host = new ServiceHost(typeof(ExtensionService));
host.Open();
Console.ReadKey();
host.Close();
}
}
}
<configuration>
<!--略-->
...................
</configuration>
客户端
namespace Test
{
class Program
{
static void Main(string[] args)
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew))
{
ShowTransactionMessage("start");
ExampleServiceReference.ExampleServiceClient exampleService1 = new
ExampleServiceReference.ExampleServiceClient();
exampleService1.Method1();
ShowTransactionMessage("exampleService started");
ExtensionServiceReference.ExtensionServiceClient extensionService = new
ExtensionServiceReference.ExtensionServiceClient();
extensionService.DoWork();
ShowTransactionMessage("extensionService started");
exampleService1.Close();
extensionService.Close();
scope.Complete();
}
Console.ReadKey();
}
//检查事务的状态
static void ShowTransactionMessage(string data)
{
if (Transaction.Current != null)
{
Transaction transaction = Transaction.Current;
string info = string.Format(" DistributedIndentifier:{0} \n LocalIndentifier:{1}",
transaction.TransactionInformation.DistributedIdentifier,
transaction.TransactionInformation.LocalIdentifier);
Console.WriteLine(data+" \n" + info);
}
}
}
}
4. 事务的的隔离性
事务的隔离性是通过TransactionIsolationLevel来定义的,它存在以下几个级别:
Unspecified | |
ReadUncommitted | 在读取数据时保持共享锁以避免读取已修改的数据,但在事务结束前这些数据可能已更改,因此会导致不可重复的读取和虚假数据。 |
ReadCommitted | 发出共享锁定并允许非独占方式的锁定。 |
RepeatableRead | 在查询中使用的所有数据上放置锁,以防止其他用户更新这些数据。这防止了不可重复的读取,但仍有可能产生虚假行。 |
Serializable | 默认级别,也是最高级别。表示事务完成前禁止外界更新数据 |
Chaos | 不使用隔离 |
Snapshot |
需要注意服务端与客户端必须使用同一级别的隔离模式,否则系统将会抛出FaultException异常。
服务类必须在至少一个方法开启了事务后才可以设置隔离模式
public interface IService
{
[OperationContract]
void Method();
}
[ServiceBehavior(TransasctionIsolationLevel=IsolationLevel.ReadCommitted)]
public class Service : IService
{
//隐式事务
[OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
public void Method()
{..........}
}
5. 事务的超时
当事务实现隔离时,资源将会被锁定,如果一些事务长期占有资源,那将容易造成死锁,为了避免这个问题,事务有一个超时限制,这个超时默认值为60s。如果事务超过此时间,即使没有发生异常,也会自动中止。
超时时候可以通过特性设置,也可使用*.config文件设置。下面的两段代码有着相同的效果,就是把超时时间设置为10s。
[ServiceBehavior(TransactionTimeout="00:00:10")]
public class Service:IService
{......}
<configuration>
........
<system.serviceModel>
........
<services>
<service name="MyService" behaviorConfiguration="myBehavior">
......
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="myBehavior" transactionTimeout="00:00:10"/>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>