• 【转】如何在项目中应用LinqToSql数据库事务


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


    代码如下:

    复制代码
    1 /*加入事务机制后的存储过程*/
    2  createprocedure sp_example
    3 @param1int= null,
    4 @param2nvarchar(20) = null
    5  as
    6 begintran tranName /*sql 事务的加入*/
    7 insertinto table0 (col1,col2,col3) values ('value1','value2','value3')
    8 update table1 set column1 =@param1where1=1
    9 --删除table2中一条已经被其他外键表引用的记录,此时会报sql引用错误
    10  delete from table2 where column3 =@param1
    11 insertinto table3 (col1,col2) values ('value1','value2')
    12 if(@@error=0)
    13 committran tranName
    14 else
    15 rollbacktran tranName
    16  go
    复制代码

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

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


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

    复制代码
    1 publicbool DeleteDepartment(int departmentId)
    2 {
    3 try
    4 {
    5 DataContext.SystemUser.DeleteOnSubmit(
    6 DataContext.SystemUser.FirstOrDefault(u => u.DepartmentID == departmentId));
    7
    8 DataContext.Department.DeleteOnSubmit(
    9 DataContext.Department.FirstOrDefault(f => f.DepartmentID == departmentId));
    10
    11 //事务机制被封装到SubmitChanges方法内
    12   DataContext.SubmitChanges();
    13
    14 returntrue;
    15 }
    16 catch
    17 {
    18 returnfalse;
    19 }
    20 }
    复制代码

    上述代码很容易理解,在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进行封装,代码如下:

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

    DBTransactionExtension
    复制代码
    1 publicstaticclass DBTransactionExtension
    2 {
    3 publicstaticbool Excute(outstring errorMsg, params Action[] actions)
    4 {
    5 //使用ReadCommitted隔离级别,保持与Sql Server的默认隔离级别一致
    6  return Excute(out errorMsg, IsolationLevel.ReadCommitted, null, actions);
    7
    8 }
    9
    10 publicstaticvoid Excute(outstring errorMsg, IsolationLevel level, params Action[] actions)
    11 {
    12 Excute(out errorMsg, level, null, actions);
    13 }
    14
    15 publicstaticvoid Excute(outstring errorMsg, int timeOut, params Action[] actions)
    16 {
    17 Excute(out errorMsg, IsolationLevel.ReadCommitted, timeOut, actions);
    18 }
    19
    20 publicstaticbool Excute(outstring errorMsg, IsolationLevel level, int? timeOut, params Action[] actions)
    21 {
    22 errorMsg ="";
    23 if (actions ==null|| actions.Length ==0)
    24 returnfalse;
    25 TransactionOptions options =new TransactionOptions();
    26
    27 options.IsolationLevel = level; //默认为Serializable,这里根据参数来进行调整
    28  
    29 if (timeOut.HasValue)
    30
    31 options.Timeout =new TimeSpan(0, 0, timeOut.Value); //默认60秒
    32  
    33 using (TransactionScope tran =new TransactionScope(TransactionScopeOption.Required, options))
    34 {
    35 try
    36 {
    37 Array.ForEach<Action>(actions, action => action());
    38 tran.Complete(); //通知事务管理器它可以提交事务
    39  returntrue;
    40 }
    41 catch (Exception ex)//回滚事务
    42   {
    43 errorMsg = ex.Message;
    44 returnfalse;
    45 }
    46 }
    47 }
    48
    49 }
    复制代码

    调用DBTransactionExtension代码如下:

    复制代码
    1 privatevoid SaveDepot(Depot depot)
    2 {
    3 DataContext.Depots.InsertOnSubmit(depot);
    4
    5 if (false)//TODO:保存库存主表前的逻辑判断,条件不满足时候调用 throw new exception执行TransactionScope回滚。
    6  thrownew Exception("自定义错误提示内容,最终由事务获取错误信息后抛给UI");
    7
    8 //条件满足则调用SubmitChanges
    9   DataContext.SubmitChanges();
    10 DepotDetail depotDetail =new DepotDetail();
    11 depotDetail.DepotID = depot.DepotID;
    12 depotDetail.Count =100;
    13
    14 DataContext.DepotDetails.InsertOnSubmit(depotDetail);
    15 //又调用了一次SubmitChanges
    16   DataContext.SubmitChanges();
    17 }
    18 public Depot InvokeTransaction(Depot depot, outstring errorMsg)
    19 {
    20 try
    21 {
    22 DBTransactionExtension.Excute(out errorMsg, () => SaveDepot(depot));
    23 return depot;
    24 }
    25 catch (Exception ex)
    26 {
    27 errorMsg = ex.Message;
    28 returnnull;
    29 }
    30 }
    复制代码

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

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


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

    另外附加自己的说明:

    TransactionScope是一种分布式事务(分布式事务处理(Distributed Transaction Processing,DTP)是指一个事务可能涉及多个数据库或多个数据源的操作)

    而DBTransaction 是针对一个数据库连接的一个事务。。。。

  • 相关阅读:
    【LeetCode 104_二叉树_遍历】Maximum Depth of Binary Tree
    【LeetCode 110_二叉树_遍历】Balanced Binary Tree
    【LeetCode 111_二叉树_遍历】Minimum Depth of Binary Tree
    【剑指Offer】36两个链表的第一个公共结点
    【剑指Offer】34第一个只出现一次的字符
    【剑指Offer】33丑数
    【剑指Offer】32把数组排成最小的数
    xgboost的原理没你想像的那么难(转载)
    【剑指Offer】31整数中1出现的次数(从1到n整数中1出现的次数)
    【剑指Offer】28连续子数组的最大和
  • 原文地址:https://www.cnblogs.com/wenghaowen/p/2725441.html
Copyright © 2020-2023  润新知