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
3) OperationBehaviorAttribute
DEMO
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);
}
}
}
}
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>
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) 运行结果如下: