C#使用TransactionScope实现事务代码
官网代码:
实际代码GK_WMS
1、TransactionScope是.Net Framework 2.0后,新增了一个名称空间。它的用途是为数据库访问提供了一个“轻量级”[区别于:SqlTransaction]的事物。使用之前必须添加对 System.Transactions.dll 的引用。
1 /// <summary> 2 /// 增加一条数据 3 /// </summary> 4 [LogHandler] 5 public bool Add(Model.LogHelper model, Model.SYS_ROLE oldModel, Model.SYS_ROLE newModel, Dictionary<Model.SYS_ROLE_MENU, Model.LogHelper> dicList) 6 { 7 bool result = false; 8 BLL.SYS_ROLE_MENUBLL _linkBLL = new SYS_ROLE_MENUBLL(); 9 try 10 { 11 using (TransactionScope ts = new TransactionScope()) 12 { 13 //新增父表的内容 14 result = dal.Add(newModel); 15 //新增各个子项,过滤是放在controller 17 foreach (KeyValuePair<Model.SYS_ROLE_MENU, Model.LogHelper> item in dicList) //取出值 18 { 19 result = false; 20 result = _linkBLL.Add(item.Value as Model.LogHelper, new Model.SYS_ROLE_MENU(), 21 item.Key as Model.SYS_ROLE_MENU); 22 } 23 ts.Complete(); //除非显示调用ts.Complete()方法。否则,系统不会自动提交这个事务。如果在代码运行退出这个block后,还未调用Complete(),那么事务自动回滚了。 24 } 25 } 26 catch (Exception) 27 { 28 result = false; 29 } 30 return result; 31 }
这里执行的是一个像表头表体的实现,只要任意一个Add方法引发异常,程序流控制就会跳出 TransactionScope 的 using 语句块,随后 TransactionScope 将自行释放并回滚该事务。
由于这段代码使用了 using 语句,所以 SqlConnection 对象和 TransactionScope 对象都将被自动调用Dispose()释放。由此可见,只需添加很少的几行代码,您就可以构建出一个事务模型,这个模型可以对异常进行处理,执行结束后会自行清理,此外,它还可以对命令的提交或回滚进行管理。
TransactionScope是基于当前线程的,在当前线程中,调用Transaction.Current方法可以看到当前事务的信息。具体关于TransactionScope的使用方法,已经它的成员方法和属性,可以查看 MSDN 。
上面所看到的示例中我们使用了TransactionScope的默认设置。TransactionScope有三种模式:
TransactionScopeOptions
描述
Required 如果已经存在一个事务,那么这个事务范围将加入已有的事务。否则,它将创建自己的事务。只有当2个TransactionScope都complete的时候才能算真正成功。
RequiresNew 这个事务范围将创建自己的事务。嵌套的事务块和外层的事务块各自独立,互不影响。
Suppress 如果处于当前活动事务范围内,那么这个事务范围既不会加入氛围事务 (ambient transaction),也不会创建自己的事务。当部分代码需要留在事务外部时,可以使用该选项。
您可以在代码的任何位置上随是查看是否存在事务范围,具体方法就是查看 System.Transactions.Transaction.Current 属性。如果这个属性为“null”,说明不存在当前事务。
若要更改 TransactionScope 类的默认设置,您可以创建一个 TransactionOptions 对象,然后通过它在 TransactionScope 对象上设置隔离级别和事务的超时时间。
TransactionOptions 类有一个 IsolationLevel 属性,通过这个属性可以更改隔离级别,例如从默认的可序列化 (Serializable) 改为ReadCommitted,甚至可以改为 SQL Server 2005 引入的新的快照 (Snapshot) 级别。
(请记住,隔离级别仅仅是一个建议。大多数数据库引擎会试着使用建议的隔离级别,但也可能选择其他级别。)
此外,TransactionOptions 类还有一个 TimeOut 属性,这个属性可以用来更改超时时间(默认设置为 1 分钟)。
下列代码中使用了默认的 TransactionScope 对象及其默认构造函数。也就是说,它的隔离级别设置为可序列化 (Serializable),事务的超时时间为 1 分钟,而且 TransactionScopeOptions 的设置为 Required。
1 TransactionOptions tOpt= new TransactionOptions(); 2 3 //设置TransactionOptions模式 4 tOpt.IsolationLevel= IsolationLevel.ReadCommitted; 5 6 // 设置超时间隔为2分钟,默认为60秒 7 tOpt.Timeout= new TimeSpan(0,2,0); 8 9 string cnString= ConfigurationManager.ConnectionStrings["sql2005DBServer"].ConnectionString); 10 using (TransactionScope tsCope= new TransactionScope(TransactionScopeOption.RequiresNew, tOpt)) 11 { 12 using (SqlConnection cn2005= new SqlConnection(cnString) 13 { 14 SqlCommand cmd= new SqlCommand(updateSql1, cn2005); 15 cn2005.Open(); 16 cmd.ExecuteNonQuery(); 17 } 18 tsCope.Complete(); 19 }
嵌套应用
如下列代码,假设 Method1 创建一个 TransactionScope,针对一个数据库执行一条命令,然后调用 Method2。Method2 创建一个自身的 TransactionScope,并针对一个数据库执行另一条命令。
1 private void Method1() 2 { 3 using (TransactionScope ts= new TransactionScope(TransactionScopeOption.Required)) 4 { 5 using (SqlConnection cn2005= new SqlConnection()) 6 { 7 SqlCommand cmd= new SqlCommand(updateSql1, cn2005); 8 cn2005.Open(); 9 cmd.ExecuteNonQuery(); 10 } 11 12 Method2(); 13 ts.Complete(); 14 } 15 } 16 17 private void Method2() 18 { 19 using (TransactionScope ts= new TransactionScope(TransactionScopeOption.RequiresNew)) 20 { 21 using (SqlConnection cn2005= new SqlConnection()) 22 { 23 SqlCommand cmd= new SqlCommand(updateSql2, cn2005); 24 cn2005.Open(); 25 cmd.ExecuteNonQuery(); 26 } 27 ts.Complete(); 28 } 29 }
总结:
进入和退出事务都要快,这一点非常重要,因为事务会锁定宝贵的资源。最佳实践要求我们在需要使用事务之前再去创建它,在需要对其执行命令前迅速打开连接, 执行动作查询 (Action Query),并尽可能快地完成和释放事务。在事务执行期间,您还应该避免执行任何不必要的、与数据库无关的代码,这能够防止资源被毫无疑义地锁定过长的 时间。
对于多个不同服务器之间的数据库操作,TransactionScope依赖DTC(Distributed Transaction Coordinator)服务完成事务一致性。但是对于单一服务器数据,TransactionScope的机制则比较复杂。主要用的的是线程静态特性。
线程静态特性ThreadStaticAttribute让CLR知道,它标记的静态字段的存取是依赖当前线程,而独立于其他线程的。既然存储在线程静态字段中的数据只对存储该数据的同一线程中所运行的代码可见,那么,可使用此类字段将其他数据从一个方法传递到该第一个方法所调用的其他方法,而且完全不用担心其他线程会破坏它的工作。TransactionScope 会将当前的 Transaction 存储到线程静态字段中。当稍后实例化 SqlCommand 时(在此 TransactionScope 从线程局部存储中删除之前),该 SqlCommand 会检查线程静态字段以查找现有 Transaction,如果存在则列入该 Transaction 中。通过这种方式,TransactionScope 和 SqlCommand 能够协同工作,从而开发人员不必将 Transaction 显示传递给 SqlCommand 对象。实际上,TransactionScope 和 SqlCommand 所使用的机制非常复杂。