【问题标题】:Using Transactions or SaveChanges(false) and AcceptAllChanges()?使用事务或 SaveChanges(false) 和 AcceptAllChanges()?
【发布时间】:2010-10-23 08:51:36
【问题描述】:

我一直在调查交易,似乎只要我将false 传递给SaveChanges() 然后在没有错误的情况下调用AcceptAllChanges(),他们就会在 EF 中自行处理:

SaveChanges(false);
// ...
AcceptAllChanges();

如果出现问题怎么办?我不需要回滚,或者,只要我的方法超出范围,事务就结束了吗?

在事务中途分配的任何 indentiy 列会发生什么情况?我想如果在我的坏之前其他人在我之后添加了一条记录,那么这意味着将会缺少 Identity 值。

有什么理由在我的代码中使用标准的TransactionScope 类吗?

【问题讨论】:

  • This 帮助我理解了为什么 SaveChanges(fase); ... AcceptAllChanges(); 首先是一种模式。请注意上述问题的公认答案是如何由blog 的作者撰写的——并且该博客在另一个问题中被引用。这一切都在一起。

标签: c# entity-framework transactions


【解决方案1】:

大多数时候使用实体框架SaveChanges() 就足够了。这会创建一个事务,或参与任何环境事务,并在该事务中完成所有必要的工作。

虽然SaveChanges(false) + AcceptAllChanges() 配对有时很有用。

最有用的地方是在您想要跨两个不同的上下文进行分布式事务的情况下。

即像这样(不好):

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save and discard changes
    context1.SaveChanges();

    //Save and discard changes
    context2.SaveChanges();

    //if we get here things are looking good.
    scope.Complete();
}

如果context1.SaveChanges() 成功但context2.SaveChanges() 失败,则整个分布式事务被中止。但不幸的是,Entity Framework 已经丢弃了context1 上的更改,因此您无法重播或有效记录失败。

但是如果你把你的代码改成这样:

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save Changes but don't discard yet
    context1.SaveChanges(false);

    //Save Changes but don't discard yet
    context2.SaveChanges(false);

    //if we get here things are looking good.
    scope.Complete();
    context1.AcceptAllChanges();
    context2.AcceptAllChanges();

}

虽然对SaveChanges(false) 的调用将必要的命令发送到数据库,但上下文本身并没有改变,因此您可以在必要时再次执行此操作,或者您可以根据需要询问ObjectStateManager

这意味着如果事务实际上引发了异常,您可以通过重试或在某处记录每个上下文 ObjectStateManager 的状态来进行补偿。

更多信息请参见myblog post

【讨论】:

  • 太好了,谢谢...所以如果出现故障,我不需要回滚吗? SaveChanges,将其标记为已保存,但在我接受所有更改之前不会真正提交..但如果出现问题..我需要回滚不是吗,以便我的对象恢复到正确的状态?
  • @Mark:如果你的意思是“回滚”,将你的对象恢复到它们在数据库中的状态,那么不,你不想这样做,因为你会丢失用户对对象的所有更改。 SaveChanges(false) 对数据库进行实际更新,而 AcceptAllChanges() 告诉 EF,“好的,您可以忘记需要保存的内容,因为它们已成功保存。”如果SaveChanges(false) 失败,则永远不会调用AcceptAllChanges(),EF 仍会认为您的对象具有已更改的属性并需要保存回数据库。
  • 您能建议如何使用 Code First 执行此操作吗? SaveChanges 或 AcceptAllChanges 方法没有参数
  • 我问了一个关于在 Code First here987654323@ 中使用这种技术的问题
  • 这在 EF 6.1 中不再可能。您知道现在需要进行哪些调整才能正常工作吗?
【解决方案2】:

如果您使用的是 EF6(实体框架 6+),则数据库调用 SQL 的情况已更改。
见:http://msdn.microsoft.com/en-us/data/dn456843.aspx

使用 context.Database.BeginTransaction。

来自 MSDN:

using (var context = new BloggingContext()) 
{ 
    using (var dbContextTransaction = context.Database.BeginTransaction()) 
    { 
        try 
        { 
            context.Database.ExecuteSqlCommand( 
                @"UPDATE Blogs SET Rating = 5" + 
                    " WHERE Name LIKE '%Entity Framework%'" 
                ); 

            var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
            foreach (var post in query) 
            { 
                post.Title += "[Cool Blog]"; 
            } 

            context.SaveChanges(); 

            dbContextTransaction.Commit(); 
        } 
        catch (Exception) 
        { 
            dbContextTransaction.Rollback(); //Required according to MSDN article 
            throw; //Not in MSDN article, but recommended so the exception still bubbles up
        } 
    } 
} 

【讨论】:

  • 在事务中使用“using”时,不需要try-catch with roolback。
  • 我正在采取一个例外来捕获这样的异常。它会导致数据库操作以静默方式失败。由于 SO 的性质,有人可能会采用此示例并将其用于生产应用程序。
  • @B2K:好点子,但是这段代码是从linkedMicrosoft 文章中复制而来的。我希望没有人在生产中使用 他们的 代码:)
  • @Robert 根据 MSDN 文章 Rollback() 是必要的。他们故意为 TransactionScope 示例省略了回滚命令。 @B2K 我在 MSDN sn-p 中添加了 throw; 并明确指出它不是 MSDN 文章中的原件。
  • (If correct) This might clear things up: 听起来 EF + MSSQL 不需要回滚,但 EF + 其他 SQL 提供程序可能。由于 EF 应该不知道它正在与哪个数据库通信,因此会调用 Rollback() 以防它正在与 MySql 或没有这种自动行为的东西进行通信。
【解决方案3】:

因为某些数据库可以在 dbContextTransaction.Commit() 处抛出异常,所以最好这样:

using (var context = new BloggingContext()) 
{ 
  using (var dbContextTransaction = context.Database.BeginTransaction()) 
  { 
    try 
    { 
      context.Database.ExecuteSqlCommand( 
          @"UPDATE Blogs SET Rating = 5" + 
              " WHERE Name LIKE '%Entity Framework%'" 
          ); 

      var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
      foreach (var post in query) 
      { 
          post.Title += "[Cool Blog]"; 
      } 

      context.SaveChanges(false); 

      dbContextTransaction.Commit(); 

      context.AcceptAllChanges();
    } 
    catch (Exception) 
    { 
      dbContextTransaction.Rollback(); 
    } 
  } 
} 

【讨论】:

  • 我正在采取一个例外来捕获这样的异常。它会导致数据库操作以静默方式失败。由于 SO 的性质,有人可能会采用此示例并将其用于生产应用程序。
  • 这与this other answer 本质上不是相同的吗?MSDN page 引用了它?我看到的唯一区别是您将false 传递给context.SaveChanges();,并另外调用context.AcceptAllChanges();
  • @B2K 不需要回滚 - 如果事务不起作用,则不会提交任何内容。对 Rollback 的显式调用也可能失败 - 请在此处查看我的回答 stackoverflow.com/questions/41385740/…
  • 回滚不是我反对的。此答案的作者更新了他们的代码以重新引发异常,从而解决了我反对的问题。
  • 抱歉,我是用手机评论的。 Todd 重新抛出异常,eMeL 没有。捕获中应该有一些东西可以通知开发人员或用户导致回滚的问题。这可能是写入日志文件、重新抛出异常或向用户返回消息。
猜你喜欢
  • 1970-01-01
  • 2014-09-07
  • 2012-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-07
相关资源
最近更新 更多