我一直在调查事务,似乎它们在EF中照顾自己,只要我传递false给SaveChanges(),然后调用AcceptAllChanges()如果没有错误:

SaveChanges(false);
// ...
AcceptAllChanges();

万一出了问题怎么办?我不需要回滚吗?或者,只要我的方法超出范围,事务就结束了吗?

在事务进行到一半时分配的标识列会发生什么?我假设如果其他人在我的记录变坏之前添加了一个记录,那么这意味着将会有一个缺失的身份值。

是否有理由在代码中使用标准的TransactionScope类?


当前回答

   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 } } }

   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);
    }

如果使用此方法,则事务范围选项隔离级别中的问题必须删除此选项

对于实体框架,大多数时候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的状态来进行补偿。

详见我的博客文章。

因为一些数据库可以在dbContextTransaction.Commit()抛出异常,所以这样更好:

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(false); 

      dbContextTransaction.Commit(); 

      context.AcceptAllChanges();
    } 
    catch (Exception) 
    { 
      dbContextTransaction.Rollback(); 
    } 
  } 
}