EF:如何在事务中调用 SaveChanges 两次?

Posted

技术标签:

【中文标题】EF:如何在事务中调用 SaveChanges 两次?【英文标题】:EF: How do I call SaveChanges twice inside a transaction? 【发布时间】:2012-09-30 08:53:14 【问题描述】:

使用实体框架(在我的例子中首先是代码),我有一个操作需要我调用 SaveChanges 来更新数据库中的一个对象,然后再次调用 SaveChanges 来更新另一个对象。 (我需要第一个 SaveChanges 来解决 EF 无法确定首先更新哪个对象的问题)。

我试过了:

using (var transaction = new TransactionScope())

    // Do something

    db.SaveChanges();

    // Do something else

    db.SaveChanges();

    tramsaction.Complete();

当我运行它时,我在第二个SaveChanges 调用中得到一个异常,说“底层提供程序在打开时失败”。内部异常说我的机器上没有启用 MSDTC。

现在,我在其他地方看到了描述如何启用 MSDTC 的帖子,但似乎我还需要启用网络访问等。这听起来完全是矫枉过正,因为没有涉及其他数据库,更不用说其他服务器。我不想做会降低整个应用程序安全性(或速度变慢)的事情。

肯定有更轻量级的方式来做这件事(最好没有 MSDTC)?!

【问题讨论】:

您使用的是 SQL 2008 吗?根据您的实际逻辑,您可以打开多个连接而无需升级。当 DTC 被调用时,这是一篇很棒的帖子:Transaction scope Automatically escalating to MSDTC 如果您想在一次事务中完成所有操作,保存一次或多次保存所有内容有什么区别?在这两种情况下,要么保存所有内容,要么不保存任何内容,因此我看不到多次保存的任何优势。根据下面的评论 - 在 Sql Server 2005 中,在事务中打开多个连接(即使源相同)会导致事务被提升为分布式事务。这在 Sql Server 2008 中得到了改进,您可以在 trx 中打开到同一数据源的多个连接,而不会导致升级 @markoreta:我使用的是 SQL Server 2012。我没有(或想要)多个连接,也不想使用 MSDTC! @pawel:我不明白。我认为每个 SaveChanges 都会使用自己的事务,因此第一个可能会成功,第二个可能会失败并回滚,从而使我的数据库不一致。这就是为什么我认为我需要外部交易。我错了吗? 如果存在环境事务,您的连接将自动注册到此事务中,并且 SavaChanges 不会创建新事务。此外,SqlServer 并不真正支持嵌套事务(即嵌套的 Begin Tran 被忽略) 【参考方案1】:

我知道这是一个迟到的答案,但我发现分享它很有用。

现在在 EF6 中,使用 dbContext.Database.BeginTransaction()

更容易实现这一目标

像这样:

using (var context = new BloggingContext())

    using (var dbContextTransaction = context.Database.BeginTransaction())
    
        try
        
            // do your changes
            context.SaveChanges();

            // do another changes
            context.SaveChanges();

            dbContextTransaction.Commit();
        
        catch (Exception)
        
            dbContextTransaction.Rollback();
        
    

更多信息请查看this

又是 EF6 以后的版本

【讨论】:

谢谢,这更像是它! 您应该对 catch 块做更多的事情,而不仅仅是回滚并吞下异常。记录,重新抛出,... @WahidBitar:try/catch 块是否必要? using 不负责处理异常吗? (类似于在 dbContextTransaction 的 Dispose 方法中调用 Rollback @Isaac 我不确定 100%,但我知道 dispose 本身不会回滚。【参考方案2】:

这可能是由您的事务中使用的两个不同的连接引起的。尝试手动控制操作的连接:

var objectContext = ((IObjectContextAdapter)db).ObjectContext;

try 
    //Open Connection
    objectContext.Connection.Open();

    using (var transaction = new TransactionScope()) 
        // Do something

        db.SaveChanges();

        // Do something else

        db.SaveChanges();

        transaction.Complete();
    
 finally 
    //Close connection after commit
    objectContext.Connection.Close();
 

【讨论】:

如果在同一个 DbContext 中,EF6.0 有 context.Database.BeginTransaction()。但是,如果在不同的 DBContext 中进行操作,这很有效。【参考方案3】:

按原样调用 SaveChanges() 会导致数据持久保存到数据库中,并且 EF 会忘记它刚刚所做的更改。

诀窍是使用 SaveChanges(false) 以便将更改持久保存到数据库,但 EF 不会忘记它所做的更改,从而使日志记录/重试成为可能。

        var scope = new TransactionScope(
            TransactionScopeOption.RequiresNew,
            new TransactionOptions()  IsolationLevel = IsolationLevel.Serializable 
        );

        using (scope)
        
            Entities context1 = new Entities();
            // Do Stuff
            context1.SaveChanges(false);

            Entities context2 = new Entities();
            // Do Stuff
            context2.SaveChanges(false);

            scope.Complete();
            context1.AcceptAllChanges();
            context2.AcceptAllChanges();
        

附:只要您在事务范围内打开了多个连接,它就会升级为 DTC。

【讨论】:

谢谢。这听起来非常合理,尽管在我的情况下,如果出现错误,我并不关心本地上下文的状态,因为在这种情况下,无论如何我都会轰炸并破坏上下文。不过,很高兴知道。 如果第一次更新正常,第二次更新失败,你的数据库数据是否一致? 既然整个东西都包裹在TransactionScope中,那我希望如此! 确实,我的意思是在更新数据库时使用 SaveChanges(false) 将保持上下文不变,因此更容易确定函数出错的原因/位置,如果你喜欢。

以上是关于EF:如何在事务中调用 SaveChanges 两次?的主要内容,如果未能解决你的问题,请参考以下文章

在 EF 中调用 SaveChanges() 后将获取的旧记录保留在变量中

何时在 EF 6 中使用 BeginTransaction? vs SaveChanges [重复]

ef的savechanges最多执行多少条数据

实体框架 - SaveChanges 与事务

目标数据库

译第41节---EF6-事务