System.Transactions.TransactionScope为了提供一种非常方便的实现分布式事务的方式,但是在某些情况下为了阻止本地事务向分布式事务提升,我们只能通过DbTransaction进行事务控制。在进行数据库操作的时候,我们经常会遇到这样的事务场景:当某个方法单独执行的时候,它应该在一个单一的DbTransaction中执行;当多个方法一起执行的时候,它们应用共同在DbTransaction中执行。在本篇文章中我们通过对DbTransaction进行封装,提供一种类似于TransactionScope的编程方式来解决这个问题。[源代码从这里下载]

目录
一、自定义的Transaction
二、自定义CommittableTransaction和DependentTransaction
三、自定义TransactionScope
四、一个具有事务感知的DbHelper
五、三种事务控制的性能

一、自定义的Transaction

我们完全采用System.Transactions的应用编程接口的设计定义基于DbTransaction的相关类型,首选来看看具有如下定义的表示事务的Transaction类型。Transaction是一个抽象类,具有DbTransactionWrapper和Completed两个属性,前者表示对DbTransaction的封装,后者表示事务操作是否已经完成。静态属性Current表示当前事务,这是一个基于当前线程的静态字段。Rollback和Dispose直接调用了DbTransactionWrapper的同名方法。

   1: namespace Artech.Transactions
   2: {
   3:     public class DbTransactionWrapper: IDisposable
   4:     {
   5:         public DbTransactionWrapper(DbTransaction transaction)
   6:         {
   7:             this.DbTransaction = transaction;
   8:         }
   9:         public DbTransaction DbTransaction { get; private set; }
  10:         public bool IsRollBack { get; set; }
  11:         public void Rollback()
  12:         {
  13:             if (!this.IsRollBack)
  14:             {
  15:                 this.DbTransaction.Rollback();
  16:             }
  17:         }
  18:         public void Commit()
  19:         {
  20:             this.DbTransaction.Commit();
  21:         }
  22:         public void Dispose()
  23:         {
  24:             this.DbTransaction.Dispose();
  25:         }
  26:     }
  27:     public abstract class Transaction : IDisposable
  28:     {
  29:         [ThreadStatic]
  30:         private static Transaction current;
  31:  
  32:         public bool Completed { get; private set; }
  33:         public DbTransactionWrapper DbTransactionWrapper { get; protected set; }
  34:         protected Transaction() { }
  35:         public void Rollback()
  36:         {
  37:             this.DbTransactionWrapper.Rollback();
  38:         }
  39:         public DependentTransaction DependentClone()
  40:         {
  41:             return new DependentTransaction(this);
  42:         }
  43:         public void Dispose()
  44:         {
  45:             this.DbTransactionWrapper.Dispose();
  46:         }
  47:         public static Transaction Current
  48:         {
  49:             get { return current; }
  50:             set { current = value; }
  51:         }
  52:     }
  53: }

二、自定义CommittableTransaction和DependentTransaction

和定义在System.Transactions的事务一样,具体事务分为CommittableTransaction和DependentTransaction,前者可以使一个可以直接提交的独立事务,后者则是依附于某个事务的依赖事务。如下面的代码片断所示,我们直接基于某个DbTransaction来创建CommittableTransaction对象。而用于提交事务的Commit方法则直接调用DbTransaction的同名方法。

   1: public class CommittableTransaction : Transaction
   2: {
   3:     public CommittableTransaction(DbTransaction dbTransaction)
   4:     {
   5:         this.DbTransaction = dbTransaction;
   6:     }
   7:     public void Commit()
   8:     {
   9:         this.DbTransaction.Commit();
  10:     }
  11: }

下面是DependentTransaction的定义,我们直接基于某个作为内部事务的Transaction对象来创建DependentTransaction,而这个内部事务可以是一个CommittableTransaction,也可以是DependentTransaction。由于DependentTransaction只具有一个内部构造函数,所以DependentTransaction通过调用定义在Transaction的DependentClone方法来创建。

   1: public class DependentTransaction : Transaction
   2: {
   3:     public Transaction InnerTransaction { get; private set; }
   4:     internal DependentTransaction(Transaction innerTransaction)
   5:     {
   6:         this.InnerTransaction = innerTransaction;
   7:         this.DbTransaction = this.InnerTransaction.DbTransaction;
   8:     }
   9: }

三、自定义TransactionScope

我们在进行事务编程的时候只会使用到具有如下定义的TransactionScope类型。我们通过指定连接字符串名称、隔离级别以及用于创建DbProviderFactory的委托创建TransactionScope对象。在TransactionScope的构造函数中,如果通过Artech.Transactions.Transaction.Current属性表示的当前事务不存在,则根据DbProviderFactory创建DbConnection并调用BeginTransaction方法开启事务,并用被开启的DbTransaction创建CommittableTransaction对象。最终将创建的CommittableTransaction作为当前事务;服务过当前事务已经存在,则直接调用它的DependentClone方法创建的DependentTransaction作为当前事务。

   1: namespace Artech.Transactions
   2: {
   3:     public class TransactionScope: IDisposable
   4:     {
   5:         private Transaction transaction = Transaction.Current;
   6:         public bool Completed { get; private set; }
   7:  
   8:         public TransactionScope(string connectionStringName, IsolationLevel isolationLevel = IsolationLevel.Unspecified,
   9:             Func<string, DbProviderFactory> getFactory = null)
  10:         {
  11:             if (null == transaction)
  12:             {
  13:                 if (null == getFactory)
  14:                 {
  15:                     getFactory = cnnstringName => DbHelper.GetFactory(cnnstringName);
  16:                 }
  17:                 DbProviderFactory factory = getFactory(connectionStringName);
  18:                 DbConnection connection = factory.CreateConnection();
  19:                 connection.ConnectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
  20:                 connection.Open();
  21:                 DbTransaction dbTransaction = connection.BeginTransaction(isolationLevel);
  22:                 Transaction.Current = new CommittableTransaction(dbTransaction);
  23:             }
  24:             else
  25:             {
  26:                 Transaction.Current = transaction.DependentClone();
  27:             }           
  28:         }
  29:  
  30:         public void Complete()
  31:         {
  32:             this.Completed = true;
  33:         }
  34:         public void Dispose()
  35:         {
  36:             Transaction current = Transaction.Current;
  37:             Transaction.Current = transaction;
  38:             if (!this.Completed)
  39:             {
  40:                 current.Rollback();
  41:             }
  42:             CommittableTransaction committableTransaction = current as CommittableTransaction;
  43:             if (null != committableTransaction)
  44:             {
  45:                 if (this.Completed)
  46:                 {
  47:                     committableTransaction.Commit();
  48:                 }
  49:                 committableTransaction.Dispose();
  50:             }
  51:         }
  52:     }
  53: }

在事务操作完成之后必须调用Complete方法“提交事务”,此时我们将TransactionScope的Completed 属性设置为True。TransactionScope实现了IDisposable方法,Dispose方法具有这样的逻辑:先将当前状态还原成创建TransactionScope之初的状态,在还原之前先将当前事务保存下来。如果Completed属性为False,则直接调用Transaction的Rollback方法对事务进行回滚。如果Completed状态为True,而且当前事务为CommittableTransaction 则直接提交事务。

四、一个具有事务感知的DbHelper

为了演示通过捕捉当前事务来来控制具体事务的执行方式,我们写了如下一个DbHelper,其中ExecuteNonQuery用于在事务中执行指定的一段SQL。具体事务控制的逻辑是这样的:如果Artech.Transactions.Transaction.Current属性返回的事务存在,则将当前操作纳入封装的DbTransaction;如果System.Transactions.Transaction.Current属性返回的事务存在,操作的执行会自动纳入该事务中;如果上述两中环境事务均不存在,则创建一个单独的DbTransaction并将相应的操作纳入其中。

   1: public class DbHelper
   2: {
   3:     //其他成员
   4:     public int ExecuteNonQuery(string commandText, IDictionary<string, object> parameters)
   5:     {
   6:         DbConnection connection = null;
   7:         DbCommand command = this.DbProviderFactory.CreateCommand();
   8:         DbTransaction dbTransaction = null;
   9:         try
  10:         {
  11:             command.CommandText = commandText;
  12:             parameters = parameters ?? new Dictionary<string, object>();
  13:             foreach (var item in parameters)
  14:             {
  15:                 command.Parameters.Add(this.BuildDbParameter(item.Key, item.Value));
  16:             }
  17:             if (null != Artech.Transactions.Transaction.Current)
  18:             {
  19:                 command.Connection = Artech.Transactions.Transaction.Current.DbTransactionWrapper.DbTransaction.Connection;
  20:                 command.Transaction = Artech.Transactions.Transaction.Current.DbTransactionWrapper.DbTransaction;
  21:             }
  22:             else
  23:             {
  24:                 connection = this.DbProviderFactory.CreateConnection();
  25:                 connection.ConnectionString = this.ConnectionString;
  26:                 command.Connection = connection;
  27:                 connection.Open();
  28:                 if (System.Transactions.Transaction.Current == null)
  29:                 {
  30:                     dbTransaction = connection.BeginTransaction();
  31:                     command.Transaction = dbTransaction;
  32:                 }
  33:             }
  34:             int result = command.ExecuteNonQuery();
  35:             if (null != dbTransaction)
  36:             {
  37:                 dbTransaction.Commit();
  38:             }
  39:             return result;
  40:         }
  41:         catch
  42:         {
  43:             if (null != dbTransaction)
  44:             {
  45:                 dbTransaction.Rollback();
  46:             }
  47:             throw;
  48:         }
  49:         finally
  50:         {
  51:             if (null != connection)
  52:             {
  53:                 connection.Dispose();
  54:             }
  55:             if (null != dbTransaction)
  56:             {
  57:                 dbTransaction.Dispose();
  58:             }
  59:             command.Dispose();
  60:         }
  61:     }
  62: }

五、三种事务控制的性能

我们现在来测试批量操作在System.Transactions.TransactionScope、Artech.Transactions.TransactionScope和针对单独操作的DbTransaction的性能。我们在目标数据库中创建一个只包含Id和Name两个字段的数据表Users,并通过如下的CreateUser为该表添加一笔记录。

   1: static void CreateUser(string id, string name)
   2: {
   3:     string sql = "INSERT Users(ID, Name) Values(@id, @name)";
   4:     Dictionary<string, object> parameters = new Dictionary<string, object>();
   5:     parameters.Add("id", id);
   6:     parameters.Add("name", name);
   7:     DbHelper.ExecuteNonQuery(sql, parameters);
   8: }

而如下三个方法分别通过上述三种事务方式执行上面的这个方法,其中参数count为添加的数据量。

   1: private static void AddUsers1(int count)
   2: {
   3:     using (System.Transactions.TransactionScope transactionScope = new System.Transactions.TransactionScope())
   4:     {
   5:         for (int i = 0; i < count; i++)
   6:         {
   7:             CreateUser(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
   8:         }
   9:         transactionScope.Complete();
  10:     }
  11: }
  12:  
  13: private static void AddUsers2(int count)
  14: {
  15:     using (Artech.Transactions.TransactionScope transactionScope = new Artech.Transactions.TransactionScope("TestDb"))
  16:     {
  17:         for (int i = 0; i < count; i++)
  18:         {
  19:             CreateUser(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
  20:         }
  21:         transactionScope.Complete();
  22:     }
  23: }
  24:  
  25: private static void AddUsers3(int count)
  26: {
  27:     for (int i = 0; i < count; i++)
  28:     {
  29:         CreateUser(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
  30:     }
  31: }

为了剔除由于数据表现有数据量对数据访问性能的影响,我们在每次进行数据批量插入之前都回调用具有如下定义的DeleteAllUsers方法将表中的数据删除殆尽。

   1: private static void DeleteAllUsers()
   2: {
   3:     string sql = "DELETE USERS";
   4:     DbHelper.ExecuteNonQuery(sql, null);
   5: }

下面是我们最终的测试程序,我们分别测试了数据量为100、1,000、10,000和100,000的数据写入操作分别在上述三种事务控制方式的耗时。

   1: Stopwatch stopWatch = new Stopwatch();
   2:  
   3: //100
   4: Console.WriteLine("100");
   5: DeleteAllUsers();
   6: stopWatch.Restart();
   7: AddUsers1(100);
   8: Console.WriteLine("{0, -36}: {1}", "System.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
   9:  
  10: DeleteAllUsers();
  11: stopWatch.Restart();
  12: AddUsers2(100);
  13: Console.WriteLine("{0, -36}: {1}", "Artech.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
  14:  
  15: DeleteAllUsers();
  16: stopWatch.Restart();
  17: AddUsers3(100);
  18: Console.WriteLine("{0, -36}: {1}\n", "Single Transnaction", stopWatch.ElapsedMilliseconds);
  19:  
  20: //1000
  21: Console.WriteLine("1000");
  22: DeleteAllUsers();
  23: stopWatch.Restart();
  24: AddUsers1(1000);
  25: Console.WriteLine("{0, -36}: {1}", "System.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
  26:  
  27: DeleteAllUsers();
  28: stopWatch.Restart();
  29: AddUsers2(1000);
  30: Console.WriteLine("{0, -36}: {1}", "Artech.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
  31:  
  32: DeleteAllUsers();
  33: stopWatch.Restart();
  34: AddUsers3(1000);
  35: Console.WriteLine("{0, -36}: {1}\n", "Single Transnaction", stopWatch.ElapsedMilliseconds);
  36:  
  37: //10000
  38: Console.WriteLine("10000");
  39: DeleteAllUsers();
  40: stopWatch.Restart();
  41: AddUsers1(10000);
  42: Console.WriteLine("{0, -36}: {1}", "System.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
  43:  
  44: DeleteAllUsers();
  45: stopWatch.Restart();
  46: AddUsers2(10000);
  47: Console.WriteLine("{0, -36}: {1}", "Artech.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
  48:  
  49: DeleteAllUsers();
  50: stopWatch.Restart();
  51: AddUsers3(10000);
  52: Console.WriteLine("{0, -36}: {1}\n", "Single Transnaction", stopWatch.ElapsedMilliseconds);
  53:  
  54: //100000
  55: Console.WriteLine("100000");
  56: DeleteAllUsers();
  57: stopWatch.Restart();
  58: AddUsers1(100000);
  59: Console.WriteLine("{0, -36}: {1}", "System.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
  60:  
  61: DeleteAllUsers();
  62: stopWatch.Restart();
  63: AddUsers2(100000);
  64: Console.WriteLine("{0, -36}: {1}", "Artech.Transactions.TransactionScope", stopWatch.ElapsedMilliseconds);
  65:  
  66: DeleteAllUsers();
  67: stopWatch.Restart();
  68: AddUsers3(100000);
  69: Console.WriteLine("{0, -36}: {1}", "Single Transaction", stopWatch.ElapsedMilliseconds);

下面是测试程序输出结果:

   1: 100
   2: System.Transactions.TransactionScope: 28
   3: Artech.Transactions.TransactionScope: 11
   4: Single Transnaction                 : 57
   5:  
   6: 1000
   7: System.Transactions.TransactionScope: 140
   8: Artech.Transactions.TransactionScope: 83
   9: Single Transnaction                 : 469
  10:  
  11: 10000
  12: System.Transactions.TransactionScope: 1530
  13: Artech.Transactions.TransactionScope: 826
  14: Single Transnaction                 : 4659
  15:  
  16: 100000
  17: System.Transactions.TransactionScope: 13562
  18: Artech.Transactions.TransactionScope: 8346
  19: Single Transaction                  : 48277

上面的输出结果表明我们自定义的TransactionScope具有最好的性能,不过优势还不是很明显,这是由于连接池机制的原因。现在我们通过如下的方式将连接字符创对连接池的支持关闭:

   1: <configuration>
   2:   <connectionStrings>
   3:     <add name="TestDb" 
   4:          providerName="System.Data.SqlClient" 
   5:          connectionString="Data Source=.;Initial Catalog=TestDb;Integrated Security=True; Pooling=False;"/>
   6:   </connectionStrings>
   7: </configuration>

再次运行我们的程序我们就会看到三种事务处理方式在性能上的显著差异(当数据量为100,000时,采用System.Transactions.TransactionScope直接就崩溃了)

   1: 100
   2: System.Transactions.TransactionScope: 2318
   3: Artech.Transactions.TransactionScope: 13
   4: Single Transnaction                 : 310
   5:  
   6: 1000
   7: System.Transactions.TransactionScope: 2949
   8: Artech.Transactions.TransactionScope: 124
   9: Single Transnaction                 : 3623
  10:  
  11: 10000
  12: System.Transactions.TransactionScope: 32754
  13: Artech.Transactions.TransactionScope: 1213
  14: Single Transnaction                 : 30631
  15:  
  16: 100000
  17:  
  18: 未经处理的异常:  System.Data.SqlClient.SqlException: Import of Microsoft Distributed Transaction Coordinator (MS DTC) transaction failed:...

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文链接

像TransactionScope一样使用DbTransaction相关推荐

  1. TransactionScope 的基本原理简介

    C# 的事务编程 1 Db事务 DbConnection 中创建基于当前连接的 DbTransaction 2  使用TransactionScope ,创建环境事务 一旦创建,在这个环境包含的DbC ...

  2. EFCore废弃了TransactionScope取而代之的Context.Database.BeginTransaction

    TransactionScope是.net平台基于的分布式事务组件,它默认为本地事务,同时当系统有需要时可以自动提升为分布式事务,而对系统的前提是要开启MSDTC服务,必要时需要在数据库服务器与应用服 ...

  3. transactionscope mysql_TransactionScope 的基本原理简介

    C# 的事务编程 1 Db事务 DbConnection 中创建基于当前连接的 DbTransaction 2  使用TransactionScope ,创建环境事务 一旦创建,在这个环境包含的DbC ...

  4. 基于TransactionScope类的分布式隐式事务

    System.Transactions 命名空间中除了上一节中提到的基于 Transaction 类的显式编程模型,还提供使用 TransactionScope 类的隐式编程模型,它与显示编程模型相比 ...

  5. transactionscope 中的异步 处理 异常_.NET Core中TransactionScope事务处理方法介绍及注意事项...

    作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/10170712.html 今天在写CzarCms的UnitOfWork的使用使用到了这个Transacti ...

  6. Entity Framework 的事务 DbTransaction

    Entity Framework 的事务 DbTransaction public static void Transaction() {myitEntities entity = null;DbTr ...

  7. 应用 TransactionScope 报:此操作对该状态的事务无效 的错误

    如果在事务过程跨了数据库服务器(即使在同一台服务器上,两个不同的数据库实例也算跨数据库服务器),而使用 TransactionScope 却报:此操作对该状态的事务无效 的错误 是因为没有启用每台服务 ...

  8. sql中的Tran和C#中的TransactionScope介绍

    sql 中的Tran 俩种写法 第一种写法 try catch: Begin TRYBegin Tran transactionDECLARE @ServiceOrderId INT SET @Ser ...

  9. 关于TransactionScope分布式事务在Oracle下的运作

    .Net 2.0下的TransactionScope分布式事务一直在项目中很好地使用着(Sql Server),最近由于数据库改换了Oracle 10g,出现了一个问题,就是TransactionSc ...

最新文章

  1. 对于注册中心,ZooKeeper、Eureka哪个更合适?
  2. StructureMap 代码分析之Widget 之Registry 分析 (1)
  3. 混凝土地坪机器人_创新引领 快速建造丨临时设施大项目部使用机器人等五项技术刷新建设新效率...
  4. CSS常用样式及示例
  5. ESP8266 如何修改默认上电校准方式?另外为什么 ESP8266 进⼊启动模式(2,7)并触发看⻔狗复位?
  6. VC6.0: fatal error C1010: unexpected end of file while looking for precompiled head
  7. JdbcTemplate和NamedParameterJdbcTemplate
  8. 共享内存 Actor并发模型到底哪个快?
  9. redis sds的申请扩容源码
  10. [计算机网络] - TCP半连接队列和全连接队列
  11. 多个物体轮廓c语言提取算法,C++ opencv-3.4.1 提取不规则物体的轮廓
  12. 隐藏元素 mayaa
  13. python学习之路第四周汇总
  14. 24个可能你现在用不到,但应该了解的 PHP 库
  15. 7天内我面试了10家公司,如何从命中率0%到命中率至70%?
  16. 十大简单排序:桶排序
  17. 1_数据分析应掌握的Python基础
  18. idc机房建设费用_数据中心机房收费标准
  19. Vue渲染Leaflet GIS地图,边界线,点位组件
  20. 【博学谷学习记录】超强总结,用心分享 | 狂野大数据 | 环境 | hive搭建

热门文章

  1. 携程Tech Girl:做勇敢的女孩
  2. L1-069 胎压监测 (15 分)-PAT 团体程序设计天梯赛 GPLT
  3. PHP:ThinkPHP5.0下载安装和各种配置
  4. 服务器加根网线用不用修改路由器,子路由器怎么设置?【图文教程】
  5. python 频率分布直方图
  6. 如何打造高并发互动小程序,护航腾讯游戏年度发布会?
  7. cpolar一款顶好用的内网映射软件
  8. Matlab批量修改fig图片的大小、文字
  9. 服务器端口8888被占用,如何解决电脑出现服务器8080端口无法访问
  10. 谁能悄悄告诉我:EDG到底是啥?