从零开始学WCF(13)事务

本文详细介绍了WCF事务,包括原子性、一致性、隔离性和持续性等事务特性。讨论了WCF中的三种主要事务组件:WCF事务、System.Transactions事务和MSDTC事务,并详细阐述了TransactionFlowAttribute、ServiceBehaviorAttribute和OperationBehaviorAttribute在配置WCF服务事务行为中的作用。此外,还提供了DEMO指导如何配置和启用WCF事务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WCF事务概述及事务模型

事务提供一种分组方法,将一组操作分为单个不可分的执行单元。事务是指具有下列属性的操作集合:

1) 原子性:此属性可确保特定事务下完成的所有更新都已提交并保持持久,或所有这些更新都已终止并回滚到其先前状态。

2) 一致性:此属性可保证某一事务下所做的更改表示从一种一致状态转换到另一种一致状态。例如,将钱从支票账户转移到存款账户的事务并不改变整个银行账户中的钱的总额。

3) 隔离:此属性可防止事务遵循属于其他并发事务的未提交的更改。隔离在确保一种事务不能对另一种事务的执行产生意外的影响的同时,还提供一个抽象的并发。不同的事务不能互相交叉影响同一个数据。

4) 持续性: 这意味着一旦提交对托管资源(如数据库记录)的更新,即使出现失败这些更新也会保持持久。


在WCF中使用事务时,需要了解的一点是,并不是在不同事务模型之间进行选择,而是在一个集成化且一致的模型的不同层上进行操作。

三个主要事务组件:

1) WCF事务

使用WCF中的事务支持可以编写事务性服务。此外,借助于它对WS-AtomicTransaction(WS-AT)协议的支持,应用程序可以将事务流式传输到使用WCF或第三方技术生成的Web服务。

WCF服务或应用程序中,WCF事务功能提供了一些属性和配置,用于以声明方式指定基础结构应当创建、流式传输和同步事务的方式和时间。

2) System.Transactions事务

System.Transactions命名空间同事提供了一个基于Transaction类的显示编程模型和一个使用TransactionScope类的隐式编程模型(在此模型中,基础结构自动管理事务)。

3) MSDTC事务

Microsoft Distributed Transaction Coordinator(MSDTC)是一个事务管理器,它为分布式事务提供支持。


WCF在以下三个标准System.ServiceModel属性(Attribute)上提供用于配置WCF服务的事务行为的属性(Property)。

1) TransactionFlowAttribute

该属性指定服务协定中的操作是否愿意接受来自客户端的传入事务。此属性(Attribute)通过以下属性(Property)提供此控制:事务使用TransactionFlowOption枚举指定传入事务是Mandatory、Allowed还是NotAllowed。

此属性是将服务操作和与客户端的外部互操作相关的唯一属性。

2) ServiceBehaviorAttribute

该属性是指定服务协定实现的内部执行结果。
TransactionAutoCompleteOnSessionClose:该属性指定会话关闭时是否完成未完成的事务。此属性的默认值为false。如果此属性为true且传入会话正常关闭而不是由于网络或客户端故障而关闭,则会成功完成任何未完成的事务。否则,如果此属性为false或者如果会话未正常关闭,则会话关闭时任何未完成的事务将会回滚。如果此属性为true,则传入通道必须基于会话。
ReleaseServiceInstanceOnTransactionComple:此属性指定事务完成时是否释放基础服务实例。此属性的默认值为true。下一个入站消息会导致创建新的基础实例,放弃上一个实例可能保持的每个事务的任何状态。释放服务实例是服务执行的内部操作对客户端可能已经建立的任何现有连接或会话没有影响。此功能等效于COM+提供的实时激活功能。如果此属性为true,则ConcurrencyMode必须等于Single。否则,服务在启动的过程中会引发无效配置验证异常。
TransactionIsolationLevel:此属性指定用于服务内事务的隔离级别;此属性采用IsolationLevel值之一。如果本地隔离级别属性是Unspecified以外的任何值,则传入事务的隔离级别必须与本地属性的设置相匹配。否则会拒绝传入事务并将故障发回客户端。如果TransactionScopeRequired为true,且没有对事务进行流处理,则此属性确定要用于本地创建的事务Isolation值。如果IsolationLevel设置为Unspecified,则使用IsolationLevelSerializable。
TransactionTimeout:此属性指定一个时间段,在服务中创建的新事务必须在此时间段内完成。如果达到此时间时事务没有完成,则会中止事务。对于已经达到TransactionScopeRequired设置为true的任何操作以及为其创建了新事务的任何操作,TimSpan用作TransactionScope超时。该超时是从创建事务到完成两阶段提交协议的第一阶段所允许的最长时间。使用的超时值始终是TransactionTimeout属性和transactionTimeout配置设置之间的较小值。

3) OperationBehaviorAttribute

该属性指定服务实现中方法的行为。
TransactionScopeRequired:指定是否必须在活动范围内执行方法。默认为false。如果没有为方法设置OperationBehaviorAttribute属性,这也暗示着不会在事务中执行该方法。
如果操作需要事务范围,则从下列各项之一中派生该事务的源:
1) 如果客户端流动事务,则在使用该分布式事务创建的事务范围内执行此方法。
2) 使用排队传输时,使用用于对消息取消排队的事务。请注意,使用的事务不是流事务,因为它不是由消息的原始发送方提供。
3) 自定义传输尅通过TransportTransactionProperty提供事务。
4) 如果上面的任一属性均没有为事务提供外部源,则会在调用方法之前创建一个新的Transaction实例。

TransactionAutoComplete:指定在没有引发未处理的异常的情况下,在其中执行方法的事务是否自动完成。如果此属性为true,则当用户方法在未引发异常的情况下返回时,调用基础结构会自动将事务标记为“已完成”。如果此属性为false,则事务会附加到实例,并且仅当客户端调用标记为此属性等于true的后续方法时,或仅当后续方法显示调用SetTransactionComplete时,事务才会标记为“已完成”。不执行这两种方案之一会导致事务永远也不会处于“已完成”状态,其中所包含的工作也不会提交,除非将TransactionAutoCompleteOnSessionClose属性设置为true。如果此属性设置为true,则必须与会话一起使用通道,且必须将InstanceContextMode设置为PerSession。

DEMO

1) 使用自承载来创建一个WCF服务,新建一个Windows Console Application,然后添加WCF服务——CalculatorService,生成的WCF接口类ICalculatorService里输入:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace Video13.Demo1.Transactions
{
    //定义WCF服务接口类
    //SessionMode是一个必须要求会话的协定,所有的方法都要在会话内调用。
    [ServiceContract(Namespace = "http://Video13.Demo1.Transactions",SessionMode=SessionMode.Required)]
    public interface ICalculatorService
    {
        [OperationContract]
        //TransactionFlowOption是该属性指定服务协定中的操作是否愿意接受来自客户端的传入事务,标记在接口服务方法里,表明必须要使用事务流动的,否则无法调用
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        double Add(double n);

        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        double Subtract(double n);

        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        double Multiply(double n);

        [OperationContract]
        [TransactionFlow(TransactionFlowOption.Mandatory)]
        double Divide(double n);
    }
}

2) 实现该WCF接口类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Globalization;
using System.Configuration;
using System.Data.SqlClient;

namespace Video13.Demo1.Transactions
{
    //服务实现类使用ServiceBehavior来修饰
    //TransactionIsolationLevel此属性指定用于服务内事务的隔离级别,这里要添加System.Transactions类的引用才可以,设置为Serializable是设定不同的事务在操作同样的数据或操作的时候,后进来的事务只可以读,但是不允许修改。
    //TransactionTimeout此属性指定一个时间段,在服务中创建的新事务必须在此时间段内完成。如果达到此时间时事务没有完成,则会中止事务。这里设置为30秒。
    //ReleaseServiceInstanceOnTranscationComplete此属性指定事务完成时是否释放基础服务实例。此属性的默认值为true。下一个入站消息会导致创建新的基础实例,放弃上一个实例可能保持的每个事务的任何状态。释放服务实例是服务执行的内部操作对客户端可能已经建立的任何现有连接或会话没有影响。此功能等效于COM+提供的实时激活功能。如果此属性为true,则ConcurrencyMode必须等于Single。否则,服务在启动的过程中会引发无效配置验证异常。也就说当客户端调用完毕后,服务端的实例并不会被销毁,而回等着下一个客户端调用来调用。
    //TransactionAutoCompleteOnSessionClose属性指定会话关闭时是否完成未完成的事务。此属性的默认值为false。如果此属性为true且传入会话正常关闭而不是由于网络或客户端故障而关闭,则会成功完成任何未完成的事务。否则,如果此属性为false或者如果会话未正常关闭,则会话关闭时任何未完成的事务将会回滚。如果此属性为true,则传入通道必须基于会话。这里为false,也就是不会自动完成未完成的事务方法。
    [ServiceBehavior(
        TransactionIsolationLevel=System.Transactions.IsolationLevel.Serializable,
        TransactionTimeout="00:00:30",
        ReleaseServiceInstanceOnTransactionComplete=false,
        TransactionAutoCompleteOnSessionClose=false)]
    public class CalculatorService : ICalculatorService
    {
        double runningTotal;

        public CalculatorService()
        {
            Console.WriteLine("正在创建一个新的服务实例... ...");
        }

        //服务实现类中的方法的修饰使用OperationBehavior。
        //TransactionScopeRequired指定是否必须在活动范围内执行方法。默认为false。如果没有为方法设置OperationBehaviorAttribute属性,这也暗示着不会在事务中执行该方法。这里为true,表明必须要在事务范围内执行该方法。
        //TransactionAutoComplete指定在没有引发未处理的异常的情况下,在其中执行方法的事务是否自动完成。如果此属性为true,则当用户方法在未引发异常的情况下返回时,调用基础结构会自动将事务标记为“已完成”。如果此属性为false,则事务会附加到实例,并且仅当客户端调用标记为此属性等于true的后续方法时,或仅当后续方法显示调用SetTransactionComplete时,事务才会标记为“已完成”。不执行这两种方案之一会导致事务永远也不会处于“已完成”状态,其中所包含的工作也不会提交,除非将TransactionAutoCompleteOnSessionClose属性设置为true。如果此属性设置为true,则必须与会话一起使用通道,且必须将InstanceContextMode设置为PerSession。这里为true,表明当这个方法执行完毕后,则该事务会自动进行类似commit的操作。
        [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
        public double Add(double n)
        {
            //写入到数据库Log
            RecordLog(String.Format(CultureInfo.CurrentCulture, "Adding {0} to {1}", n, runningTotal));
            runningTotal = runningTotal + n;
            return runningTotal;
        }



        [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=true)]
        public double Subtract(double n)
        {
            //写入到数据库Log
            RecordLog(String.Format(CultureInfo.CurrentCulture, "Subtract {0} to {1}", n, runningTotal));
            runningTotal = runningTotal - n;
            return runningTotal;
        }

        //这里设置TransactionAutoComplete为false,意思是说该事务不会自动进行commit操作,除非在客户端继该方法后调用的方法里使用了TransactionAutoComplete为true的方法;或者是使用显示的commit操作——OperationContext.Current.SetTransactionComplete()
        [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=false)]
        public double Multiply(double n)
        {
            //写入到数据库Log
            RecordLog(String.Format(CultureInfo.CurrentCulture, "Multiply {0} to {1}", n, runningTotal));
            runningTotal = runningTotal * n;
            return runningTotal;
        }

        [OperationBehavior(TransactionScopeRequired=true,TransactionAutoComplete=false)]
        public double Divide(double n)
        {
            //写入到数据库Log
            RecordLog(String.Format(CultureInfo.CurrentCulture, "Divide {0} to {1}", n, runningTotal));
            runningTotal = runningTotal / n;
            //显示事务的提交commit操作
            OperationContext.Current.SetTransactionComplete();
            //显示的回滚操作
            //System.Transactions.Transaction.Current.Rollback();

            return runningTotal;
        }

        private void RecordLog(string recordText)
        {
            //记录操作log
            if (ConfigurationManager.AppSettings["usingSql"] == "true")
            {
                using (SqlConnection conn = new SqlConnection(ConfigurationManager.AppSettings["connectionString"]))
                {
                    conn.Open();

                    SqlCommand cmdLog = new SqlCommand("INSERT into Log (Entry) Values (@Entry)", conn);
                    cmdLog.Parameters.AddWithValue("@Entry", recordText);
                    cmdLog.ExecuteNonQuery();
                    cmdLog.Dispose();

                    SqlCommand cmdCount = new SqlCommand("SELECT COUNT(*) FROM log", conn);
                    string rowCount = cmdCount.ExecuteScalar().ToString();
                    cmdCount.Dispose();

                    Console.WriteLine("写入数据库{0}行 : {1}", rowCount, recordText);

                    conn.Close();
                }
            }
            else
            {
                Console.WriteLine("没有写入数据库:{0}", recordText);
            }
        }
    }
}



3) 自承载该方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;

namespace Video13.Demo1.Transactions
{
    class Program
    {
        static void Main(string[] args)
        {
            //自承载该服务
            using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
            {
                host.Open();
                Console.WriteLine("自承载WCF服务已启动");
                Console.ReadLine();
            }
        }
    }
}

4) 这时候点击运行该服务的话,会报错(At least one operation on the 'CalculatorService' contract is configured with the TransactionFlowAttribute attribute set to Mandatory but the channel's binding 'WSHttpBinding' is not configured with a TransactionFlowBindingElement. The TransactionFlowAttribute attribute set to Mandatory cannot be used without a TransactionFlowBindingElement.),这是由于我们没有配置配置信息的问题。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!--数据库连接-->
    <add key="usingSql" value="true"/>
    <add key="connectionString" value="Data Source=.;Initial Catalog=WCFTest;Integrated Security=True;"/>
  </appSettings>
    <system.serviceModel>
      <services>
        <service name="Video13.Demo1.Transactions.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">
          <host>
            <baseAddresses>
              <add baseAddress="http://localhost:8000/ServiceModelSamples/service"/>
            </baseAddresses>
          </host>
          <!--这里一定要设置bindingConfiguration-->
          <endpoint address="" binding="wsHttpBinding" bindingConfiguration="transactionalBinding" contract="Video13.Demo1.Transactions.ICalculatorService"/>
          <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        </service>
      </services>

      <bindings>
        <wsHttpBinding>
          <!--这里一定要设置绑定的transactionFlow为true,否则无法使用事务-->
          <binding name="transactionalBinding" transactionFlow="true"/>
        </wsHttpBinding>
      </bindings>

      <behaviors>
        <serviceBehaviors>
          <behavior name="CalculatorServiceBehavior">
            <serviceMetadata httpGetEnabled="true"/>
            <serviceDebug includeExceptionDetailInFaults="false"/>
          </behavior>
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>
</configuration>

5) 先运行该自承载服务的同时,使用SVCUTIL来生成客户端代理类。

    1. 找到如下地址“C:\Windows\System32\cmd.exe”命令行工具,右键以管理员身份运行(WIN7系统下)。

    2. 输入如下命令:“cd C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin”进入到svcuitl.exe工具所在的文件夹下。然后再输入一下代码生成代理类:

        svcutil.exe /out:c:/ClientCode.cs /config:c:/app.config http://localhost:8000/ServiceModelSamples/service


6) 创建客户端程序client,然后再把生成的ClientCode.cs文件拷贝到该项目里来。客户端里的app.config文件不要使用自动生成的,使用自动生成的会报错,这里我们手动写一个:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="transactionalBinding" transactionFlow="true" />
      </wsHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:8000/ServiceModelSamples/service"
          binding="wsHttpBinding" bindingConfiguration="transactionalBinding"
          contract="ICalculatorService">
        <!--The username and the domain over here will have to be replaced 
        by the identity under which the service will be running-->
        <!--identity>
              <userPrincipalName value="username@domain" />
         </identity-->
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

7) 然后再Main方法中调用服务端方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            CalculatorServiceClient client = new CalculatorServiceClient();

            try
            {
                //客户端必须要开始一个事务范围(添加System.Transactions引用),才能调用服务端的事务,所有的事务方法要在这个事务范围内调用。
                using (TransactionScope tx = new TransactionScope())
                {
                    Console.WriteLine("开始事务调用方法");

                    // Call the Add service operation.
                    double value = 100.00D;
                    Console.WriteLine("  Adding {0}, running total={1}",
                                                        value, client.Add(value));

                    // Call the Subtract service operation.
                    value = 45.00D;
                    Console.WriteLine("  Subtracting {0}, running total={1}",
                                                        value, client.Subtract(value));

                    // Call the Multiply service operation.
                    value = 9.00D;
                    Console.WriteLine("  Multiplying by {0}, running total={1}",
                                                        value, client.Multiply(value));

                    // Call the Divide service operation.
                    value = 15.00D;
                    Console.WriteLine("  Dividing by {0}, running total={1}",
                                                        value, client.Divide(value));

                    Console.WriteLine("  Completing transaction");
                    tx.Complete();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            Console.WriteLine("Transaction Commited");
            Console.ReadKey();
        }
    }
}

8) 运行结果如下:

我们可以看到我们在Divide方法里即使该方法并没有设置TransactionAutoComplete,但使用了OperationContext.Current.SetTransactionComplete()方法进行了该事务的Commit的操作,所以该事务还是会被提交到数据库中。但是如果使用回滚操作System.Transactions.Transaction.Current.Rollback()方法,前面所进行的所有的操作都不会记录到数据库也不会有值产生。

源代码: http://download.youkuaiyun.com/detail/eric_k1m/6469013

WCF中事务配置

WCF提供了一下三个用于为服务配置事务的属性:
transactionFlow、transactionProtocol和transactionTimeout。

配置transactionFlow

WCF提供的大多数预定义绑定都包含transactionFlow和transactionProtocol属性,以便可以使用特定的事务流协议为特定终结点配置用于接受传入事务的绑定。
transactionFlow属性指定是否为使用绑定的服务终结点启用事务流。

配置transactionProtocol

transactionProtocol属性指定要用于使用绑定的服务终结点的事务协议。
下面是一个配置节实例,该配置节将指定的绑定配置为支持事务流并且使用WS-AtomicTransaction协议:


配置transactionTimeout

可以在配置文件的behavior元素中配置WCF服务的transactionTimeout属性:



启用事务流

服务终结点的事务流设置根据下列三个值的交集生成:
1) 为服务协定中的每个方法指定的TransactionFlowAttribute属性。
2) 特定绑定中的TransactionFlow绑定属性。
3) 特定绑定中的TransactionFlowProtocol绑定属性。
TransactionFlowProtocol绑定属性允许在可用于流动事务的两个不同事务协定之间进行选择。

TransactionFlowProtocol事务流协议有一下两种:
1) WS-AtomicTransaction协议:
该协议是一个标准的协议,对于要求第三方协议堆栈具有互操作性时的情形非常有用。
2) OleTransactions协议:
该协议对于如下的情形非常有用:即不要求第三方协议堆栈具有互操作性,并且服务部署人员预先知道WS-AT协议服务将在本地禁用或者现有网络拓扑不支持使用WS-AT。

使用这些不同组合生成的不同类型的事务流:


使用ServiceModel属性模拟COM+

下表比较用于创建EnterpriseServices事务的TransactionOption枚举,以及他们如何与Syste.ServiceModel提供的WCF属性关联

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值