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() 后将获取的旧记录保留在变量中