[WCF编程]12.事务:服务事务编程(上)

1、设置环境事务

    默认状况下,服务类和操做没有环境事务,即便客户端事务传播到服务端也是如此。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

    下图所展现的是服务使用的事务,这个事务是有绑定,操做契约属性来分配的:设计

image

    在上图中,非事务客户端调用服务1,服务1的操做契约配置为TranscationFlowOption.Allowed(容许参与事务,若是调用方启用了事务,则参与),尽管绑定容许事务流传播,可是因为客户端没有包含事务,因此没法传播事务。服务1的操做配置须要事务域。所以,WCF为服务1建立了一个新的事务A。服务1随后调用其它三个服务。服务2使用的绑定也是容许事务流操做,并且操做契约强制须要客户端事务流。由于操做行为配置须要配置事务流,WCF把事务A设置为服务2的环境事务。服务3的绑定和操做契约不容许事务流传播。可是,由于服务3的操做须要事务域,WCF为此建立了一个新的事务B,并做为服务3的环境事务。与服务3相似,服务4的绑定与操做也不容许事务流 传播,可是服务4不须要事务域,因此WCF不会给它建立环境事务。3d

2、事务传播模式

    服务使用哪一个事务取决于绑定的事务流属性(两个值)、操做契约事务(三个值),以及操做行为的事务域(两个值)的设置。下表列出了可行的八种组合:日志

image

    八种组合实际产生四种有效的事务传播模式,即客户端/服务、客户端、服务和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);
        }
    }

运行结果:

00961660354fc1f08cb10dda

结论:

从图中咱们看到:
    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);
        }
    }

运行结果:

    若是客户端没有启用事务会产生异常信息

image

    若是客户端启用事务,则客户端会和服务端会使用同一事务流

a9697cd36ad7292f3bf3cfa2

    这种事务模式,服务端不能独立启动事务,服务端事务必须参与客户端事务流中,确保客户端和服务端处于同一事务流中,这样客户端和服务端的代码会作为一个原子执行,当事务流中任何一个环节产生异常都会把整个事务流进行回滚,实现很是好的一致性。

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);
        }

运行结果:

1193138266464f8d0df4d2ad

结论: 

    从图中咱们能够看到:
     服务端本地事务ID不为空,说明服务端代码仍处于事务中执行。但全局事务ID都是空的,说明这种事务模型不存在全局型事务流。
     还管客户端事务执行是否成功,服务端事务老是成功执行。

总结:
    这种事务模型通常应用于:当服务端须要在客户端事务流以外独立运行事务的状况。

4.None事务模式

    这种种事务模型中服务端代码不启用任何事务也不参与任何事务流。

实现步骤:

    a.可使用任何绑定信道。若是选择的是支持事务的绑定,须要设置TransactionFlow = false,由于服务端事务独立启动,并不须要事务流。
    b.不须要在方法契约上添加TransactionFlowAttribute声明。若是你非得设置此Attribute的话,请设置TransactionFlow(TransactionFlowOption.NotAllowed),指明服务端拒绝参与事务流。
    c.不须要在方法行为上添加TransactionScopeRequired属性声明,若是你非要设置此属性的话,请设置OperationBehavior(TransactionScopeRequired=false)。指明此方法执行过程当中不使用事务。

    即默认状况下,服务是不使用事务的。

    None事务模式容许开发非事务型服务,它能够被事务型客户端调用。

    显然这种事务模型很危险,它破坏了系统的一致性:若是客户端在执行过程当中产生异常时,将不会回滚服务端的数据。通常这在一些不会出现不一致性的系统中采用这种模型。

事务模型的选择

    上面所述的四种事务模型中,Service模型和None模型用得比较少,这两种模式带系统不一致的潜在危险。通常在须要事务流控制的地方,咱们应选择Client/Service模型或Client模型来确保客户端和服务端之间系统一致。

3、实例代码下载

image

点击下载

相关文章
相关标签/搜索