zoukankan      html  css  js  c++  java
  • 在Entity framework中使用事务

    默认情况下,当EF调用SaveChanges()时,会把生成的所有SQL命令“包”到一个“事务(transaction)”中,只要有一个数据更新操作失败,整个事务将回滚。
    在多数情况下,如果你总在数据更新操作代码中使用一个而不是多个DbContext对象,并且只是在最后调用一次SaveChanges(),那么EF的默认事务处理机制己经够用了,无需做额外的事情。
    然而,如果出现以下的情形,你就必须显式地处理事务了。


    第一种情况:你需要分阶段地保存数据,因而需要多次调用SaveChanges()或者执行修改数据库的SQL命令。



    请看以下示例代码:

    using (var context = new MyDbContext())
    {
        try
         {
            Person3 p = context.People3.First();
    
            p.Name ="newName" + (new Random().Next(1, 100));
    
            context.SaveChanges();
    
            context.Database.ExecuteSqlCommand("update Person3 setDescription={0} where Person3Id={1}",
    
                                "DescriptionModified at " + DateTime.Now.ToShortTimeString(),
    
                                p.Person3Id);
            p.age *= 2;
            context.SaveChanges();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }

    上述代码中,调用两次SaveChanges(),还有一次执行Update命令。
    如果在最后一次SaveChanges()中出现异常,虽然最后一次没成功,但你会发现前两次数据己经保存!这就带来了数据不一致的问题。
    对于这种场景,你需要显式地编写事务代码了(注:以下代码适用于EF6):

    using (var context = new MyDbContext())
     {
        using (var transaction =context.Database.BeginTransaction())
        {
            try
            {
                  ……
     
                  context.SaveChanges();
     
                  context.Database.ExecuteSqlCommand("……);
     
                  ……
     
                  context.SaveChanges();
                  transaction.Commit();
     
             }
            catch (Exception e)
            {
                   Console.WriteLine(e.Message);
                   transaction.Rollback();
            }
        }
    }

    特别要注意一定要调用commit(),我测试发现,只要不Commit,即使没有异常发生,事务仍将回滚,数据库中的数据不会更新。

    第二种情况,你需要使用多个DbContext保存数据,这种情况使用TransactionScope来提交事务


    TransactionScope位于using System.Transactions;命名空间下,需要在引用中手动加入。该类不能被继承。

        //
        // 摘要:
        //     使代码块成为事务性代码。此类不能被继承。
        public sealed class TransactionScope : IDisposable

    常用参数

    1. TransactionScopeOption
      //
      // 摘要:
      //     该范围需要一个事务。如果已经存在环境事务,则使用该环境事务。否则,在进入范围之前创建新的事务。这是默认值。
      Required = 0,
      
      //
      // 摘要:
      //     总是为该范围创建新事务。
      RequiresNew = 1,
      
      //
      // 摘要:
      //     环境事务上下文在创建范围时被取消。范围中的所有操作都在无环境事务上下文的情况下完成。
      Suppress = 2
    2. TimeSpan设置超时时间
              //
              // 摘要:
              //     以指定的超时时间值和要求初始化 System.Transactions.TransactionScope 类的新实例。
              //
              // 参数:
              //   scopeOption:
              //     System.Transactions.TransactionScopeOption 枚举的一个实例,描述与此事务范围关联的事务要求。
              //
              //   scopeTimeout:
              //     在 System.TimeSpan 之后,事务范围将超时并中止此事务。
              public TransactionScope(TransactionScopeOption scopeOption, TimeSpan scopeTimeout);

    优点:

    1. 使用起来比较方便.TransactionScope可以实现隐式的事务,使你可以在写数据访问层代码的时候不用考虑到事务,而在业务层的控制事务.
    2. 可以实现分布式事务,比如跨库或MSMQ.
    3. 在EntityFramework中可以解决DbContextTransaction的多个上下文出现死锁问题。也就是说在EF中使用TransactionScope事务时,不用考虑数据库操作的多上下文问题。

    缺点:

    1. 此种方式对于数据库锁定表,会影响其他进程的查询,也就是对于锁定的表,查询也锁定,不允许出现脏读,其他的查询需要挂起等待。重点是不能进行配置修改。
    2. 此种方式对于同一个范围/同一个逻辑操作,需要进行多线程的锁定处理,不然多个线程开启同一个事务会抛出异常。
    3. 多个事务操作中,存在锁定同一个表的时候也会出现死锁现象

    使用实例如下:

    Test1 _context = new Test1();
    Test1 _context2 = new Test1();
    
    using (TransactionScope tran = new TransactionScope())
    {
        try
        {
            //1.修改省
            Area province = _context.Areas.FirstOrDefault(q => q.AreaLevel == 1);
            province.AreaName = province.AreaName + "1";
            _context.SaveChanges();
            Console.WriteLine(_context2.Areas.FirstOrDefault(q => q.AreaLevel == 1).AreaName);
    
            //2.修改市
            Area city = _context2.Areas.FirstOrDefault(q => q.AreaLevel == 2);
            city.AreaName = city.AreaName + "1";
            _context2.SaveChanges();
    
            //抛出异常
            throw new Exception("测试事务异常");
            tran.Complete();
        }
    
        catch (Exception ex)
        {
            Console.WriteLine("执行出错:" + ex.Message);
        }
    }

    从上面的代码我们可以看出来Entity Framework中,DbContex(_context和_context2)更新数据的数据库连接都是在调用SaveChanges方法时创建和关闭的,所以只要保证DbContex(_context和_context2)的SaveChanges方法都在TransactionScope的using代码块内调用,就可以保证DbContex(_context和_context2)更新数据的数据库连接都是在同一个事务中。

    特别说明:
    如果你的上下文(DbContext)在程序中只有一个,那么如果执行事务期间出现异常造成事务回滚,会出现数据模型和数据库中数据不对应情况,也就是说数据库的操作虽然被回滚了,但是EF中每个数据模型的状态却变成了事务成功提交后的了,但是实际上事务被回滚了。针对这个问题目前官方给的建议是,EF中如果事务回滚,就抛弃老的上下文(DbContext),使用一个新的上下文(DbContext)。

    参考文献:

    EntiryFramework中事务操作(二)TransactionScope

    EntityFramework 事务处理

  • 相关阅读:
    汉字乱码、加密后结果字符串不一致
    msgpack和TParams互相转换
    unigui监听会话开始和结束
    System.JSON.Builders.pas
    保证最终一致性的模式
    使用 Delta Sharing 协议进行数据共享
    dremio 16 升级问题
    graylog 4.0 运行
    supabase 开源firebase 可选工具
    cube.js 最新playground 说明
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9799586.html
Copyright © 2011-2022 走看看