实体框架6事务回滚


80

使用EF6,您可以使用新交易,例如:

using (var context = new PostEntityContainer())
        {
            using (var dbcxtransaction = context.Database.BeginTransaction())
            {
                try
                {
                    PostInformation NewPost = new PostInformation()
                    {
                        PostId = 101,
                        Content = "This is my first Post related to Entity Model",
                        Title = "Transaction in EF 6 beta"
                    };
                    context.Post_Details.Add(NewPost);
                    context.SaveChanges();
                    PostAdditionalInformation PostInformation = new PostAdditionalInformation()
                    {
                        PostId = (101),
                        PostName = "Working With Transaction in Entity Model 6 Beta Version"
                    };

                    context.PostAddtional_Details.Add(PostInformation);
                    context.SaveChanges();

                    dbcxtransaction.Commit();
                }
                catch
                {
                    dbcxtransaction.Rollback();
                }
            }
        }

当事情横盘整理时,是否真的需要回滚?我很好奇,因为“提交”说明中说:“提交基础商店交易。”

而“回滚”说明说:“回滚基础商店交易。”

这让我感到好奇,因为在我看来,如果不调用Commit,以前执行的命令将不会被存储(对我来说这是合乎逻辑的)。但是,如果是这种情况,调用Rollback函数的原因是什么?在EF5中,我使用了TransactionScope,它没有回滚功能(只有完整功能),这对我来说似乎很合理。由于MS DTC的原因,我不能再使用TransactionScope,但也不能像上面的示例那样使用try catch(即,我只需要Commit)。


1
您是否已阅读sql中的事务?EF试图模仿这一点。AFAIK,如果您不使用sql提交事务,则它将回滚。
gunr2171 2014年


是的,我了解SQL本身中的事务。我很好奇EF的作用,但是如果他们模仿它,那是有道理的。我看看是否可以解决它。谢谢!
Cookies狗

SaveChanges()总是在事务中发生,如果发生异常,该事务将回滚。在您的情况下,无需尝试手动进行处理(在这种情况下,最好SaveChanges仅一次添加所有实体一次)。
Pawel 2014年

我只希望在两个都没有失败时从两个SaveChanges中保存项目,所以是的,我确实需要围绕它们两个进行一次事务处理。
Cookies狗

Answers:


116

您不需要Rollback手动调用,因为您正在使用该using语句。

DbContextTransaction.Dispose方法将在using块的末尾被调用。如果未成功提交事务(未调用或遇到异常),它将自动回滚事务。以下是SqlInternalTransaction.Dispose方法的源代码(DbContextTransaction.Dispose使用SqlServer提供程序时将最终委托给它):

private void Dispose(bool disposing)
{
    // ...
    if (disposing && this._innerConnection != null)
    {
        this._disposing = true;
        this.Rollback();
    }
}

您会看到,它检查是否_innerConnection不为null,如果不为null,则回滚事务(如果已提交,则为_innerConnectionnull)。让我们看看有什么Commit作用:

internal void Commit() 
{
    // Ignore many details here...

    this._innerConnection.ExecuteTransaction(...);

    if (!this.IsZombied && !this._innerConnection.IsYukonOrNewer)
    {
        // Zombie() method will set _innerConnection to null
        this.Zombie();
    }
    else
    {
        this.ZombieParent();
    }

    // Ignore many details here...
}

internal void Zombie()
{
    this.ZombieParent();

    SqlInternalConnection innerConnection = this._innerConnection;

    // Set the _innerConnection to null
    this._innerConnection = null;

    if (innerConnection != null)
    {
        innerConnection.DisconnectTransaction(this);
    }
}

23

只要您始终将SQL Server与EF结合使用,就无需显式使用catch来调用Rollback方法。允许using块自动回滚任何异常将始终有效。

但是,从实体框架的角度考虑它时,您会看到为什么所有示例都使用显式调用来回滚事务。对于EF而言,数据库提供程序是任意的和可插入的,并且可以使用MySQL或具有EF提供程序实现的任何其他数据库替换该提供程序。因此,从EF的角度来看,由于EF不知道数据库提供程序的实现,因此不能保证提供程序将自动回滚已处置的事务。

因此,作为最佳实践,EF文档建议您显式回滚-以防万一有一天您将提供程序更改为在处置时不会自动回滚的实现。

在我看来,任何优秀且编写良好的提供程序都将自动在Dispose中回滚事务,因此用try-catch-rollback将所有内容包装在using块内的额外努力是过大的。


1
感谢您的见解。我研究了代码,然后您到达了抽象类DbTransaction的Dispose,该类在SqlTransaction中被覆盖,该类本身称为Mouhong Lin提到的SqlInternalTransaction。
ShawnFumo 2015年

4
  1. 由于您已经编写了一个“使用”块来实例化事务,因此无需明确提及“回滚”功能,因为在处置时它将自动回滚(除非已提交)。
  2. 但是,如果您在不使用using块的情况下实例化它,那么在发生异常(准确地在catch块中)的情况下回滚事务是非常重要的,并且对于空健的代码也应使用null检查。BeginTransaction的工作方式不同于事务范围(如果成功完成所有操作,则只需要一个完整的功能)。相反,它类似于Sql事务的工作。
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.