我一直在调查事务,似乎它们在EF中照顾自己,只要我传递false给SaveChanges(),然后调用AcceptAllChanges()如果没有错误:
SaveChanges(false);
// ...
AcceptAllChanges();
万一出了问题怎么办?我不需要回滚吗?或者,只要我的方法超出范围,事务就结束了吗?
在事务进行到一半时分配的标识列会发生什么?我假设如果其他人在我的记录变坏之前添加了一个记录,那么这意味着将会有一个缺失的身份值。
是否有理由在代码中使用标准的TransactionScope类?
对于实体框架,大多数时候SaveChanges()就足够了。这将创建一个事务,或在任何环境事务中登记,并在该事务中执行所有必要的工作。
有时SaveChanges(false) + AcceptAllChanges()配对是有用的。
最有用的地方是在希望跨两个不同上下文执行分布式事务的情况下。
例如,像这样(坏的):
using (TransactionScope scope = new TransactionScope())
{
//Do something with context1
//Do something with context2
//Save and discard changes
context1.SaveChanges();
//Save and discard changes
context2.SaveChanges();
//if we get here things are looking good.
scope.Complete();
}
如果context1.SaveChanges()成功,但context2.SaveChanges()失败,整个分布式事务将中止。但不幸的是,实体框架已经丢弃了context1上的更改,所以您不能重放或有效地记录失败。
但是如果你把代码修改成这样:
using (TransactionScope scope = new TransactionScope())
{
//Do something with context1
//Do something with context2
//Save Changes but don't discard yet
context1.SaveChanges(false);
//Save Changes but don't discard yet
context2.SaveChanges(false);
//if we get here things are looking good.
scope.Complete();
context1.AcceptAllChanges();
context2.AcceptAllChanges();
}
虽然调用SaveChanges(false)将必要的命令发送到数据库,但上下文本身并没有改变,因此可以在必要时再次执行此操作,也可以在需要时询问ObjectStateManager。
这意味着如果事务实际抛出异常,您可以通过重新尝试或在某处记录每个上下文ObjectStateManager的状态来进行补偿。
详见我的博客文章。
public static TransactionScope CreateAsyncTransactionScope(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
var transactionOptions = new TransactionOptions
{
/* IsolationLevel = isolationLevel,*/
Timeout = TransactionManager.MaximumTimeout,
};
return new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled);
}
如果使用此方法,则事务范围选项隔离级别中的问题必须删除此选项
如果您使用的是EF6(实体框架6+),则数据库调用SQL的情况已经更改。
参见:https://learn.microsoft.com/en-us/ef/ef6/saving/transactions
使用context.Database.BeginTransaction。
从MSDN:
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand(
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'"
);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
dbContextTransaction.Commit();
}
catch (Exception)
{
dbContextTransaction.Rollback(); //Required according to MSDN article
throw; //Not in MSDN article, but recommended so the exception still bubbles up
}
}
}
对于实体框架,大多数时候SaveChanges()就足够了。这将创建一个事务,或在任何环境事务中登记,并在该事务中执行所有必要的工作。
有时SaveChanges(false) + AcceptAllChanges()配对是有用的。
最有用的地方是在希望跨两个不同上下文执行分布式事务的情况下。
例如,像这样(坏的):
using (TransactionScope scope = new TransactionScope())
{
//Do something with context1
//Do something with context2
//Save and discard changes
context1.SaveChanges();
//Save and discard changes
context2.SaveChanges();
//if we get here things are looking good.
scope.Complete();
}
如果context1.SaveChanges()成功,但context2.SaveChanges()失败,整个分布式事务将中止。但不幸的是,实体框架已经丢弃了context1上的更改,所以您不能重放或有效地记录失败。
但是如果你把代码修改成这样:
using (TransactionScope scope = new TransactionScope())
{
//Do something with context1
//Do something with context2
//Save Changes but don't discard yet
context1.SaveChanges(false);
//Save Changes but don't discard yet
context2.SaveChanges(false);
//if we get here things are looking good.
scope.Complete();
context1.AcceptAllChanges();
context2.AcceptAllChanges();
}
虽然调用SaveChanges(false)将必要的命令发送到数据库,但上下文本身并没有改变,因此可以在必要时再次执行此操作,也可以在需要时询问ObjectStateManager。
这意味着如果事务实际抛出异常,您可以通过重新尝试或在某处记录每个上下文ObjectStateManager的状态来进行补偿。
详见我的博客文章。