博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[转载]如何在LinqToSql项目中应用TransactionScope数据库事务
阅读量:5250 次
发布时间:2019-06-14

本文共 9810 字,大约阅读时间需要 32 分钟。

     本文主要涉及LinqToSql数据库事务相关,文章不足之处,欢迎您指出。

     一、回顾T-SQL中的事务机制:


代码如下:

/*加入事务机制后的存储过程*/ create procedure sp_example  @param1 int = null,  @param2 nvarchar(20) = null as   begin tran tranName /*sql 事务的加入*/   insert into table0 (col1,col2,col3) values ('value1','value2','value3')   update table1 set column1 = @param1 where 1=1   --删除table2中一条已经被其他外键表引用的记录,此时会报sql引用错误   delete from table2 where column3 = @param1       insert into table3 (col1,col2) values ('value1','value2')   if(@@error =0)      commit tran tranName   else     rollback tran tranName go

以上代码是一个具备事务机制的简单存储过程,需要指出的是当上述代码执行到第十行时,此时如果该存储过程未加入事务机制那么势必会导致第10行之前已经被影响的数据库记录也不会被还原(rollback)。这样的代码是我们不想见到的,所以事务在复杂的商业逻辑中保持数据的完整性还是尤为重要的。

 

    二、LinqToSql 中的SubmitChanges内置事务机制:


众所周知LinqToSql 中我们的事务机制代码变的相对简单了,如以下代码:

public bool DeleteDepartment(int departmentId)        {            try            {                DataContext.SystemUser.DeleteOnSubmit(                                   DataContext.SystemUser.FirstOrDefault(u => u.DepartmentID == departmentId));                DataContext.Department.DeleteOnSubmit(                                    DataContext.Department.FirstOrDefault(f => f.DepartmentID == departmentId));                //事务机制被封装到SubmitChanges方法内                 DataContext.SubmitChanges();                return true;            }            catch            {                return false;            }        }

上述代码很容易理解,在LinqToSql 为了删除一条部门记录。我们首选要删除该部门被引用的外键表记录这里是员工表,(以上代码只是为举例用,实际开发中是不会有此种业务的)当外键记录都删除成功后代码执行到第8行,这时才能能删除部门对象。否则报SqlException外键引用无法删除部门记录。我们唯一需要做的只是将 DataContext.SubmitChanges();这句放在所有Linq操作数据库语句之后这样就可以调用数据库事务机制了。比如当第5行代码执行时SystemUser还被Order表引用。当SubmitChanges执行时会自动调用transaction.Rollback()方法回滚SubmitChanges()之前的所有被影响的数据库记录,详情请阅Reflector。

 

   三、在LinqToSql中SubmitChange内置事务机制无法满足的业务场景:


      当程序需要处理更多更复杂的商业逻辑时,我发现光凭SubmitChange方法自带的事务机制是远远不能满足的。

      该场景描述如下:

       如果为完成某一个特定的业务,需要在程序中使用多次的SubmitChanges方法。比如我们要做一个库存相关业务,该业务是由两张表组成:主表+从表。分别为主表:Depot和从表:DepotDetail 两张表。两张表关系如下:

        当我们通过LinqToSql生成一个库存对象时其实应先生成Depot对象后再将生成Depot对象的DepotID(主键)传递到DepotDetail对象中用于生成库存明细表记录。也就说为了生成库存明细表记录我们必须先生成Depot主表,那样就不得不先调用SubmitChanges方法,当保存DepotDetail对象时还需要再一次调用SubmitChanges()方法。因为调用了多次SubmitChanges方法所以SubmitChanges内置的回滚机制已经不能满足需要了。

 

  四、TransactionScope的应用:


     我们需要引用.net 的System.Transactions 类库使用TransactionScope类,帮我们更有效的处理数据库事务机制。对TransactionScope进行封装,代码如下:

     本段代码,被我稍稍改造如下:

/// /// 数据库事务处理扩展类/// 
详情查看:
///
public static partial class DBTransactionExtension{ /// /// 事务处理,默认IsolationLevel.ReadCommitted,TransactionScopeOption.Required,Timeout=60秒 /// /// 事务处理过程中引发的异常信息 /// 方法数组,
封装一个方法,该方法不具有参数并且不返回值。 ///
事务处理成功,返回true,否则返回false
public static bool Excute(out string errorMsg, params Action[] actions) { //使用ReadCommitted隔离级别,保持与Sql Server的默认隔离级别一致 return Excute(out errorMsg, IsolationLevel.ReadCommitted, TransactionScopeOption.Required, null, actions); } /// /// 指定Timeout,事务处理,默认IsolationLevel.ReadCommitted,TransactionScopeOption.Required /// /// 事务处理过程中引发的异常信息 /// 该事务的超时时间(秒) /// 方法数组,
封装一个方法,该方法不具有参数并且不返回值。 ///
事务处理成功,返回true,否则返回false
public static bool Excute(out string errorMsg, int timeOut, params Action[] actions) { return Excute(out errorMsg, IsolationLevel.ReadCommitted, TransactionScopeOption.Required, timeOut, actions); } /// /// 指定IsolationLevel,事务处理,默认TransactionScopeOption.Required /// /// 事务处理过程中引发的异常信息 /// /// 指定事务的隔离级别,枚举说明 ///
IsolationLevel.Chaos: 无法改写隔离级别更高的事务中的挂起的更改。
///
IsolationLevel.ReadCommitted: 不可以在事务期间读取可变数据,但是可以修改它。
///
IsolationLevel.ReadUncommitted: 可以在事务期间读取和修改可变数据。
///
IsolationLevel.RepeatableRead: 可以在事务期间读取可变数据,但是不可以修改。可以在事务期间添加新数据。
///
IsolationLevel.Serializable: 可以在事务期间读取可变数据,但是不可以修改,也不可以添加任何新数据。
///
IsolationLevel.Snapshot: 可以读取可变数据。
在事务修改数据之前,它验证在它最初读取数据之后另一个事务是否更改过这些数据。
如果数据已被更新,则会引发错误。这样使事务可获取先前提交的数据值。
在尝试提升以此隔离级别创建的事务时,将引发一个 InvalidOperationException,
并产生错误信息“Transactions with IsolationLevel Snapshot cannot be promoted”
(无法提升具有 IsolationLevel 快照的事务)。
///
IsolationLevel.Unspecified: 正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。
如果设置了此值,则会引发异常。
/// /// 方法数组,
封装一个方法,该方法不具有参数并且不返回值。 ///
事务处理成功,返回true,否则返回false
public static bool Excute(out string errorMsg, IsolationLevel level, params Action[] actions) { return Excute(out errorMsg, level, TransactionScopeOption.Required, null, actions); } /// /// 指定IsolationLevel和Timeout,事务处理,默认TransactionScopeOption.Required /// /// 事务处理过程中引发的异常信息 /// /// 指定事务的隔离级别,枚举说明 ///
IsolationLevel.Chaos: 无法改写隔离级别更高的事务中的挂起的更改。
///
IsolationLevel.ReadCommitted: 不可以在事务期间读取可变数据,但是可以修改它。
///
IsolationLevel.ReadUncommitted: 可以在事务期间读取和修改可变数据。
///
IsolationLevel.RepeatableRead: 可以在事务期间读取可变数据,但是不可以修改。可以在事务期间添加新数据。
///
IsolationLevel.Serializable: 可以在事务期间读取可变数据,但是不可以修改,也不可以添加任何新数据。
///
IsolationLevel.Snapshot: 可以读取可变数据。
在事务修改数据之前,它验证在它最初读取数据之后另一个事务是否更改过这些数据。
如果数据已被更新,则会引发错误。这样使事务可获取先前提交的数据值。
在尝试提升以此隔离级别创建的事务时,将引发一个 InvalidOperationException,
并产生错误信息“Transactions with IsolationLevel Snapshot cannot be promoted”
(无法提升具有 IsolationLevel 快照的事务)。
///
IsolationLevel.Unspecified: 正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。
如果设置了此值,则会引发异常。
/// /// 该事务的超时时间(秒) /// 方法数组,
封装一个方法,该方法不具有参数并且不返回值。 ///
事务处理成功,返回true,否则返回false
public static bool Excute(out string errorMsg, IsolationLevel level, int? timeOut, params Action[] actions) { return Excute(out errorMsg, level, TransactionScopeOption.Required, timeOut); } /// /// 指定IsolationLevel和Timeout,事务处理,默认TransactionScopeOption.Required /// /// 事务处理过程中引发的异常信息 /// /// 指定事务的隔离级别,枚举说明 ///
IsolationLevel.Chaos: 无法改写隔离级别更高的事务中的挂起的更改。
///
IsolationLevel.ReadCommitted: 不可以在事务期间读取可变数据,但是可以修改它。
///
IsolationLevel.ReadUncommitted: 可以在事务期间读取和修改可变数据。
///
IsolationLevel.RepeatableRead: 可以在事务期间读取可变数据,但是不可以修改。可以在事务期间添加新数据。
///
IsolationLevel.Serializable: 可以在事务期间读取可变数据,但是不可以修改,也不可以添加任何新数据。
///
IsolationLevel.Snapshot: 可以读取可变数据。
在事务修改数据之前,它验证在它最初读取数据之后另一个事务是否更改过这些数据。
如果数据已被更新,则会引发错误。这样使事务可获取先前提交的数据值。
在尝试提升以此隔离级别创建的事务时,将引发一个 InvalidOperationException,
并产生错误信息“Transactions with IsolationLevel Snapshot cannot be promoted”
(无法提升具有 IsolationLevel 快照的事务)。
///
IsolationLevel.Unspecified: 正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。
如果设置了此值,则会引发异常。
/// /// /// 提供用于创建事务范围的附加选项,枚举说明 ///
TransactionScopeOption.Required: 如果已经存在一个事务,那么这个事务范围将加入已有的事务。否则,它将创建自己的事务。
///
TransactionScopeOption.RequiresNew: 这个事务范围将创建自己的事务。
///
TransactionScopeOption.Suppress: 如果处于当前活动事务范围内,
那么这个事务范围既不会加入氛围事务 (ambient transaction),也不会创建自己的事务。
当部分代码需要留在事务外部时,可以使用该选项。
/// /// 该事务的超时时间(秒) /// 方法数组,
封装一个方法,该方法不具有参数并且不返回值。 ///
事务处理成功,返回true,否则返回false
public static bool Excute(out string errorMsg, IsolationLevel level, TransactionScopeOption scopeOption, int? timeOut, params Action[] actions) { errorMsg = string.Empty; if (actions == null || actions.Length == 0) { return false; } //IsolationLevel默认为Serializable,这里根据参数来进行调整 var transactionOptions = new TransactionOptions { IsolationLevel = level }; if (timeOut.HasValue) { transactionOptions.Timeout = new TimeSpan(0, 0, timeOut.Value); //默认60秒 } using (var tran = new TransactionScope(scopeOption, transactionOptions)) { try { Array.ForEach(actions, action => action()); tran.Complete(); //通知事务管理器它可以提交事务 return true; } catch (Exception ex)//回滚事务 { errorMsg = ex.Message; return false; } } }}

调用DBTransactionExtension代码如下:

private void SaveDepot(Depot depot)        {            DataContext.Depots.InsertOnSubmit(depot);            if (false)//TODO:保存库存主表前的逻辑判断,条件不满足时候调用 throw new exception执行TransactionScope回滚。                 throw new Exception("自定义错误提示内容,最终由事务获取错误信息后抛给UI");            //条件满足则调用SubmitChanges            DataContext.SubmitChanges();            DepotDetail depotDetail = new DepotDetail();            depotDetail.DepotID = depot.DepotID;            depotDetail.Count = 100;            DataContext.DepotDetails.InsertOnSubmit(depotDetail);            //又调用了一次SubmitChanges             DataContext.SubmitChanges();        } public Depot InvokeTransaction(Depot depot, out string errorMsg)        {            try            {                DBTransactionExtension.Excute(out errorMsg, () => SaveDepot(depot));                return depot;            }            catch (Exception ex)            {                errorMsg = ex.Message;                return null;            }        }

根据上述调用方法,我们已经可以在LinqToSql中灵活的使用数据库事务了。

 

  五、TransactionScope类使用的注意事项:


    使用TransactionScope时如果调用多次LinqToSql的DataContext对象实例(等同调用多个数据库连接),那么我们必须开启MSDTC否则事务不能正常工作,具体请阅。注:TransactionScope 适用于多种 Data Provider 比如 oracle 、OleDB、ODBC等。

   最后希望本篇文章能给您带来帮助。

 

本文出处:

转载自:

转载于:https://www.cnblogs.com/VAllen/articles/In-LinqToSql-Use-TransactionScope.html

你可能感兴趣的文章
Leetcode: Unique Binary Search Trees II
查看>>
C++ FFLIB 之FFDB: 使用 Mysql&Sqlite 实现CRUD
查看>>
Spring-hibernate整合
查看>>
c++ map
查看>>
exit和return的区别
查看>>
Django 相关
查看>>
比较安全的获取站点更目录
查看>>
空间分析开源库GEOS
查看>>
前端各种mate积累
查看>>
Python(软件目录结构规范)
查看>>
Windows多线程入门のCreateThread与_beginthreadex本质区别(转)
查看>>
Nginx配置文件(nginx.conf)配置详解1
查看>>
linux php编译安装
查看>>
redis哨兵集群、docker入门
查看>>
hihoCoder 1233 : Boxes(盒子)
查看>>
codeforces水题100道 第二十二题 Codeforces Beta Round #89 (Div. 2) A. String Task (strings)
查看>>
c++||template
查看>>
[BZOJ 5323][Jxoi2018]游戏
查看>>
条件断点 符号断点
查看>>
Python Web框架Django (五)
查看>>