实体框架6事务回滚

有了 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 描述说: “回滚基础存储事务。”

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

76656 次浏览

You don't need to call Rollback manually because you are using the using statement.

DbContextTransaction.Dispose method will be called in the end of the using block. And it will automatically rollback the transaction if the transaction is not successfully committed (not called or encountered exceptions). Following is the source code of SqlInternalTransaction.Dispose method (DbContextTransaction.Dispose will finally delegate to it when using SqlServer provider):

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

You see, it checks if _innerConnection is not null, if not, rollback the transaction (if committed, _innerConnection will be null). Let's see what Commit does:

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);
}
}
  1. Since you have written a 'using' block to instantiate the transaction, you do not need to mention the Rollback function explicitly since it would be automatically rolled back (unless it was committed) at the time of disposal.
  2. But if you instantiate it without a using block, in that case it is essential to rollback the transaction in case of an exception (precisely in a catch block) and that too with a null check for a more robust code. The working of BeginTransaction is unlike transaction scope (which just needs a complete function if all the operations were completed successfully). Instead, it is akin to the working of Sql transactions.

As long as you will always be using SQL Server with EF, there is no need to explicitly use the catch to call the Rollback method. Allowing the using block to automatically rollback on any exceptions will always work.

However, when you think about it from the Entity Framework point of view, you can see why all examples use the explicit call to Rollback the transaction. To the EF, the database provider is arbitrary and pluggable and the provider can be replaced with MySQL or any other database that has an EF provider implementation. Therefore, from the EF point of view, there is no guarantee that the provider will automatically rollback the disposed transaction, because the EF does not know about the implementation of the database provider.

So, as a best practice, the EF documentation recommends that you explicitly Rollback -- just in case you someday change providers to an implementation that does not auto-rollback on dispose.

In my opinion, any good and well written provider will automatically rollback the transaction in the dispose, so the additional effort to wrap everything inside the using block with a try-catch-rollback is overkill.