使用事务还是SaveChanges(false)和AcceptAllChanges()?


346

我一直在研究交易和看来,他们照顾自己的EF只要我传递falseSaveChanges(),然后调用AcceptAllChanges(),如果没有错误:

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

如果出现问题怎么办?我不必回滚,或者一旦方法超出范围,交易是否结束?

在事务中途分配的所有intentiy列会如何处理?我想如果有人在我的坏事发生之前在我的事后添加了一条记录,那么这意味着将丢失一个Identity值。

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


1
帮助我理解了为什么首先SaveChanges(fase); ... AcceptAllChanges();是一种模式。请注意如何接受的答案对上述问题,由笔者写博客 -这博客是在其他问题中引用。全部融合在一起。
红豌豆

Answers:


451

使用实体框架,大多数时间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()失败,则整个分布式事务中止。但是很遗憾,实体框架已经放弃了对的更改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某个地方重试或记录每个上下文的状态来进行补偿。

有关更多信息,请参见我的 博客文章


3
太好了,谢谢...所以,如果出现故障,我不必回滚吗?SaveChanges,将其标记为已保存,但直到我接受allchanges才真正提交。.但是如果出现问题..我将需要回滚,这样我的对象才能返回到正确的状态吗?
马克·史密斯,2009年

33
@Mark:如果通过“回滚”表示将对象恢复为它们在数据库中的状态,则不会,因为您将丢失用户对对象的所有更改,因此您不想这样做。 SaveChanges(false)确实对数据库进行了更新,同时AcceptAllChanges()告诉EF:“好的,您可以忘记需要保存哪些内容,因为已成功保存了它们。” 如果SaveChanges(false)失败,AcceptAllChanges()将永远不会调用,并且EF仍将您的对象视为具有已更改的属性,需要将其保存回数据库。
BlueRaja-Danny Pflughoeft,2010年

您能建议使用Code First做到这一点吗?SaveChanges或AcceptAllChanges方法没有参数
Kirsten Greed

2
我曾问一个问题关于使用与代码第一次这种技术 在这里
基尔斯滕贪婪

13
在EF 6.1中不再可能。您知道现在需要进行哪些调整才能工作吗?
2014年

113

如果您使用的是EF6(Entity Framework 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
        } 
    } 
} 

51
在事务上使用“使用”时,不需要使用roolback进行try-catch。
罗伯特

12
我正在捕获这样的异常。它导致数据库操作静默失败。由于SO的性质,有人可能会举这个例子并在生产应用程序中使用它。
B2K

3
@ B2K:很好,但是此代码是从链接的 Microsoft文章中复制的。我希望没人能在生产中使用他们的代码:)
J Bryan Price

6
@Robert根据MSDN文章Rollback()是必需的。他们故意为TransactionScope示例省略了“回滚”命令。我已将@ B2K添加throw;到MSDN代码段中,并清楚地表明它不是MSDN文章中的原始内容。
Todd

6
(如果正确)这可能会解决问题:听起来像EF + MSSQL不需要回滚,但是EF +其他SQL提供程序可能需要。由于EF应该与它正在与哪个数据库无关,因此可以Rollback()在与MySql或没有自动行为的情况下被调用。
像贾里德(Jared)2013年

-5

因为某些数据库可以在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(); 
    } 
  } 
} 

7
我正在捕获这样的异常。它导致数据库操作静默失败。由于SO的性质,有人可能会举这个例子并在生产应用程序中使用它。
B2K

6
这是否与其他引用归因于其引用的MSDN页面的答案基本相同?我看到的唯一区别是您false进入context.SaveChanges();,并另外致电context.AcceptAllChanges();
李慧夏

@ B2K不需要回滚-如果事务不起作用,则不会提交任何内容。另外要回滚显式调用可能会失败-在这里看到我的回答stackoverflow.com/questions/41385740/...
肯·

回滚不是我反对的。这个答案的作者更新了他们的代码以重新抛出异常,从而解决了我反对的问题。
B2K

抱歉,我用手机发表了评论。Todd重新引发异常,eMeL不会。捕获中应该有一些东西可以通知开发人员或用户导致回滚的问题。那可能是写入日志文件,重新引发异常或向用户返回消息。
B2K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.