谈谈分布式事务之三: System.Transactions事务详解[下篇]
在前面一篇给出的Transaction的定义中,信息的读者应该看到了一个叫做DepedentClone的方法。该方法对用于创建基于现有Transaction对象的“依赖事务(DependentTransaction)”。不像可提交事务是一个独立的事务对象,依赖事务依附于现有的某个事务(可能是可提交事务,也可能是依赖事务)。依赖事务可以帮助我们很容易地编写一些事务型操作,当环境事务不存的时候,可以确保操作在一个独立的事务中执行;当环境事务存在的时候,则自动加入其中。 一、依赖事务(Dependent Transaction)依赖事务通过DependentTransaction类型表示,DependentTransaction定义如下。和CommittableTransaction一样,DependentTransaction也是Transaction的子类。既然DependentTransaction依赖于现有的Transaction对象而存在,相当于被依赖事务的子事务,所以无法执行对事务的提交,也自然不会定义Commit方法。但是,DependentTransaction具有一个唯一的方法成员:Complete。调用这个方法意味着向被依赖事务发送通知,表明所有与依赖事务相关的操作已经完成。 1: [Serializable]
3: { 5: } 1、通过DependentTransaction将异步操所纳入现有事务 通过Transaction的静态属性Current表示的环境事务保存在TLS(Thread Local Storage)中,所以环境事务是基于当前线程的。这就意味着,即使环境事务存在,通过异步调用的操作也不可能自动加入到当前事务之中,因为在异步线程中感知不到环境事务的存在。在这种情况下,我们需要做的就是手工将当前事务传递到另一个线程中,作为它的环境事务。通过依赖事务我们很容易实现这一点。 DependentTransaction通过Transaction的DependentClone方法创建,该方法具有一个DependentCloneOption枚举类型的参数,体现了被依赖的事务再上尚未接受到依赖事务的通知(调用Complete或者Rollback方法)得情况下,提交或者完成所采取的事务控制行为。DependentCloneOption提供了两个选项,BlockCommitUntilComplete表示被依赖事务会一直等待接收到依赖事务的通知或者超过事务设定的超时时限;而RollbackIfNotComplete则会直接将被依赖的事务回滚,并抛出TransactionAbortedException异常。 3: {
5: public DependentTransaction DependentClone(DependentCloneOption cloneOption);
7: enum DependentCloneOption
9: BlockCommitUntilComplete, 11: } 下面的代码演示了如果通过依赖事务,采用异步的方式进行银行转账操作。借助于组件ThreadPool,将主线程环境事务的依赖事务传递给异步操作代理,开始异步操作的时候将此依赖事务作为当前的环境事务,那么之后的操作将自动在当前事务下进行。 2: {
4: CommittableTransaction transaction = new CommittableTransaction();
6: { 8: ThreadPool.QueueUserWorkItem(state => 10: Transaction.Current = state as DependentTransaction;
13: Withdraw(accountFrom,amount); 15: (state as DependentTransaction).Complete();
17: catch (Exception ex)
19: Transaction.Current.Rollback(ex); 21: finally
23: (state as IDisposable).Dispose();
25: } 27: //其他操作
29: } 31: { 33: Console.WriteLine("转帐失败,错误信息:{0}",ex.InnerException.Message);
35: 36: {
38: throw;
40: finally
42: Transaction.Current = originalTransaction; 44: } void InvokeInTransaction(Action action) 6: if (null == Transaction.Current) 8: committableTransaction = 9: Transaction.Current = committableTransaction;
11: else
13: dependentTransaction = Transaction.Current.DependentClone(DependentCloneOption.RollbackIfNotComplete); 15: } 17: 18: {
20: null != committableTransaction)
22: committableTransaction.Commit(); 24:? 26: { 28: } 32: Transaction.Current.Rollback(ex); 37: Transaction transaction = Transaction.Current; 39: transaction.Dispose(); 41: } InvokeInTransaction方法的参数是一个Action类型的代理(Delegate),表示具体的业务操作。在开始的时候记录下当前的环境事务,当整个操作结束之后应该环境事务恢复成该值。如果存在环境事务,则创建环境事务的依赖事务,反之直接创建可提交事务。并将新创建的依赖事务或者可提交事务作为当前的环境事务。将目标操作的执行(action)放在try/catch中,当目标操作顺利执行后,调用依赖事务的Complete方法或者可提交事务的Commit方法。如果抛出异常,则调用环境事务的Rollback进行回滚。在finally块中将环境事务恢复到之前的状态,并调用Dispose方法对创建的事务进行回收。 借助于InvokeInTransaction这个辅助方法,我们以事务型方法的形式定义了如下的两个方法:Withdraw和Deposit,分别实现提取和存储的操作。 3: Dictionary<string,1)">object> parameters = new Dictionary<object>();
5: parameters.Add("amount",1)" id="lnum6"> 6: InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_WITHDRAW",parameters)); 8:? 11: Dictionary< 12: parameters.Add( 13: parameters.Add( 14: InvokeInTransaction(() => DbAccessUtil.ExecuteNonQuery("P_DEPOSIT",1)" id="lnum15"> 15: } 二、TransactionScope在上面一节,我结合可提交事务和依赖事务,以及环境事务的机制提供了对事务型操作的实现。实际上,如果借助TransactionScope,相应的代码将会变得非常简单。下面的代码中,通过TransactionScope对InvokeInTransaction进行了改写,从执行效果来看这和原来的代码完全一致。 4: {
6: transactionScope.Complete(); 8: } 通过InvokeInTransaction方法前后代码的对比,我们可以明显看到TransactionScope确实能够使我们的事务控制变得非常的简单。实际上,在利用System.Transactions事务进行编程的时候,我们一般不会使用到可提交事务,对于依赖事务也只有在异步调用的时候会使用到,基于TransactionScope的事务编程方式才是我们推荐的。 正如其名称所表现的一样,TransactionScope就是为一组事务型操作创建一个执行范围,而这个范围始于TransactionScope创建之时,结束于TransactionScope被回收(调用Dispose方法)。在对TransactionScope进行深入介绍之前,照例先来看看它的定义: public TransactionScope();
public TransactionScope(TransactionScopeOption scopeOption); 7: public TransactionScope(TransactionScopeOption scopeOption,TimeSpan scopeTimeout);
9: 10: 11: 14: } 我们可以看到TransactionScope实现了IDisposable接口,除了Dispose方法之外,仅仅具有一个唯一的方法:Complete。但是TransactionScope却有一组丰富的构造函数。我们先来看看这些构造函数相应的参数如何影响TransactionScope对事务控制的行为。 1、TransactionScopeOption 实际上前面一节中提供的InvokeInTransaction方法基本上体现了TransactionScope的内部实现。也就是说,TransactionScope也是通过创建可提交事务或者依赖事务,并将其作为事务范围内的环境事务,从而将范围的所有操作纳入到一个事务之中。 通过在构造函数中指定TransactionScopeOption类型的scopeOption参数,控制TransactionScope当环境事务存在的时候应该采取怎样的方式执行事务范围内的操作。具有来讲,具有三种不同的方式:
TransactionScopeOption是一个枚举,三个枚举值Required、RequiresNew和Suppress依次对应上面的三种行为。 3: Required,1)" id="lnum4"> 4: RequiresNew,
6: } 对于Required选项,如果当前存在环境事务TransactionScope会创建环境事务的依赖事务,负责创建可提交事务,然后将创建的环境事务或者可提交事务作为事务范围的环境事务。如对于RequiresNew选项,TransactionScope总是会创建可提交事务并将其作为事务范围的环境事务,意味着控制事务范围内操作的事务也当前的环境事务已经没有任何关系。如果Suppress选项,TransactionScope会将事务范围内的环境事务设为空,意味着事务范围内的操作并不受事务的控制。 Required是默认选项,意味着事务范围内的事务将会作为当前环境事务的一部分。如果你不希望某个操作被纳入当前的环境事务,但是相应的操作也需要事务的控制以确保所操作数据的一致性。比如,当业务逻辑失败导致异常抛出,需要对相应的错误信息进行日志记录。对于日记的操作就可以放入基于RequiresNew选项创建TransactionScope中。对于一些不重要的操作(操作的错误可被忽略),并且不需要通过事务来控制的操作,比如发送一些不太重要的通知,就可以采用Suppress选项。 2、TransactionOptions和EnterpriseServicesInteropOption TransactionOptions在前面已经提及,用于控制事务的超时时限和隔离级别。对于超时时限,你也可以选择TransactionScope相应能够的构造函数以TimeSpan的形式指定。而对于事务的隔离级别,需要着重强调一点:当选择TransactionScopeOption.Required选项时,TransactionScope指定的隔离级别必须与环境事务(如果有)相匹配。 比如下面的例子中,我定义两个嵌套的TransactionScope,外部的TransactionScope采用默认的隔离级别,内部在采用ReadCommitted隔离级别,当执行这段代码的时候,会抛出如图1所示的ArgumentException异常。 using (TransactionScope innerScope = new TransactionScope(TransactionScopeOption.Required,transactionOptions))
6: //事务型操作
8: } 10: outerScope.Complete(); enum EnterpriseServicesInteropOption 6: } 3、事务提交和回滚 对于事务范围中的事务,无论是事务的提交(对于可提交事务)、完成(依赖事务)和回滚都是在Dispose方法中执行的。TransactionScope中定一个个私有的布尔类型字段(complete)表示事务是否正常结束。该成员的默认值为False,当调用TransactionScope的Complete方法的时候会将此字段设置成True。当Dispose执行的时候,如果该字段的值为False,会调用事务的Rollback方法对该事务实施回滚;否则会调用Commit方法(对于可提交事务)对事务进行提交或者调用Complete方法(依赖事务)通知被依赖的事务本地事务已经正常完成。 除了执行事务的提交、完成或者回滚之外,TransactionScope的Dispose方法还负责将环境事务回复到事务范围开始之前的状态。在调用Complete和Dispose之前,环境事务处于不可用的状态,如果此时试图获取环境事务,会抛出异常。比如在下面的代码中,在事务范围内部调用Complete方法后,通过Transaction的Current静态属性获取当前环境事务,会抛出图2所示的InvalidOpertionException异常。 4: transactionScope.Complete();
6: } 分布式事务系列: 作者:Artech 出处:http://artech.cnblogs.com 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- ASP.NET图表在数字旁边添加百分比
- asp.net – 如何通过webservice从返回的数据集中删除“diff
- 在ASP.NET MVC Web API服务和MVC客户端体系结构中实现身份验
- ASP.NET C#静态变量是全局的?
- Asp.net视图状态MAC的验证失败
- asp.net-mvc – 使用具有Razor视图的Spark主布局
- asp.net – 我怎么知道我的应用程序中是否需要“WCF HTTP激
- 在ASP.NET中,HTML指令符号<%#或<%= etc?在服务器端执行
- asp.net-mvc – Razor – 渲染没有Render()且没有编码
- 在ASP.NET中开发SharePoint Web部件
- asp.net – 这个基于LINQ的搜索是否可以安全地防
- asp.net-mvc – 在ASP.NET MVC应用程序中进行分页
- asp.net – 尝试为.mdf文件附加自动命名的数据库
- asp.net-mvc – Spark视图引擎中的HTML注释
- asp.net-web-api – Web API和.NET 4.5:声明和权
- asp.net-mvc – 在显示模板中使用DisplayFor
- asp.net 4.0:是否有相当于ClientIDMode的INPUT的
- asp.net – 如何在没有代码隐藏文件的情况下在VB
- asp.net – 查找应用程序根URL而不使用?
- asp.net-mvc – ScriptBundle中的{version}是什么