Entity Framework 6事务回滚

90

使用 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();
                }
            }
        }

当事情出现问题时,是否真的需要回滚?我很好奇,因为“提交”说明中写道:“提交基础存储事务。”

而“回滚”说明则是:“回滚基础存储事务。”
这让我很好奇,因为在我看来,如果没有调用提交函数,则之前执行的命令将不会被存储(这对我来说似乎是合理的)。但如果是这种情况,调用回滚函数的原因将是什么?在EF5中,我使用了TransactionScope,它没有回滚函数(只有完成函数),这对我来说似乎是合理的。由于MS DTC的原因,我不能再使用TransactionScope,但我也不能像上面的示例一样使用try catch(即,我只需要提交)。


2
你有没有阅读关于SQL事务的相关内容?EF试图模仿它。据我所知,如果你在SQL中没有提交一个事务,它就会被回滚。 - gunr2171
请参考这个问题 - gunr2171
是的,我知道 SQL 中的事务。我只是好奇 EF 是如何处理的,但如果它们模仿了 SQL 的话,那就说得通了。我会看看能否解决这个问题。谢谢! - The Cookies Dog
SaveChanges()总是在一个事务中发生,如果出现异常,该事务将被回滚。在您的情况下,没有必要尝试手动处理此问题(在这种特定情况下,最好先添加所有实体,然后仅执行一次SaveChanges)。 - Pawel
我只希望在两个SaveChanges都不失败时保存项目,因此,是的,我确实需要将它们包装在一个单一的事务中。 - The Cookies Dog
显示剩余2条评论
3个回答

138

由于您正在使用using语句,因此无需手动调用Rollback

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

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

你看,它检查_innerConnection是否为null,如果不是,则回滚事务(如果已提交,则_innerConnection将为null)。让我们看看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);
    }
}

32
只要您始终使用带有EF的SQL Server,就无需显式使用catch调用Rollback方法。 允许using块在任何异常上自动回滚将始终起作用。
然而,从Entity Framework的角度来看,您可以看到为什么所有示例都使用显式调用Rollback事务。对于EF而言,数据库提供程序是任意且可插入的,该提供程序可以替换为具有EF提供程序实现的MySQL或任何其他数据库。因此,从EF的角度来看,不能保证提供程序将自动回滚已释放的事务,因为EF不知道数据库提供程序的实现。
因此,作为最佳实践,EF文档建议您显式执行Rollback - 以防将来更改为不会在dispose时自动回滚的实现。
在我看来,任何良好编写的提供程序都将在dispose中自动回滚事务,因此用try-catch-rollback将所有内容包装在using块内需要额外的努力。

1
谢谢您的见解。我深入研究了代码,最终到达了抽象类DbTransaction的Dispose方法,该方法被SqlTransaction重写,而SqlTransaction本身调用了Mouhong Lin提到的SqlInternalTransaction。 - ShawnFumo

5
  1. 由于您已经编写了'using'块来实例化事务,因此无需显式提及Rollback函数,因为在处理完成后(除非已提交),它将自动回滚。
  2. 但是,如果没有使用块进行实例化,则必须在出现异常时回滚事务(确切地说,在catch块中进行回滚),并且还需要进行null检查以获得更强大的代码。BeginTransaction的工作方式不同于事务范围(如果所有操作都成功完成,则仅需要一个complete函数)。相反,它类似于Sql事务的工作方式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接