DbContext.SaveChanges() 在 TransactionScope 中的可见性

DbContext.SaveChanges() visibility within a TransactionScope

给定一个包含 2 个随后打开的 DbContext 的 TransactionScope,第一个上下文保存的更改是否保证在第二个上下文的范围内可见?

var txOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (var transaction = new TransactionScope(TransactionScopeOption.Required, txOptions))
{
    using (var context1 = new MyDbContext())
    {
        context1.Employees.Single(e => e.Id == 1).Salary += 1; // Update
        context1.Employees.Remove(context1.Employees.Single(e => e.Id == 2)); // Delete
        context1.Employees.Add(new Employee { Id = 3 }); // Add

        context1.SaveChanges();
    }

    using (var context2 = new MyDbContext())
    {
        // are changes saved by context1 guaranteed to be visible here?
    }

    transaction.Complete();
}

通常我会期望它们是,但后来我看到“No, this was a coincidence because the 2nd context reused the connection of the 1st from the connection pool. This is not guaranteed and will break under load.”让我感到困惑。谁能证实或反驳这一点?

My understanding is that all efforts 将确保您的线程从池中接收相同的连接,前提是您在请求另一个连接之前关闭每个连接,并且连接字符串相同。重复使用同一连接将阻止事务升级为 DTC。

但是,如果您仍然需要进一步的保证,可以使用一个 overload of the DbContext 构造函数来获取现有连接。这样就可以保证两个Context使用同一个Connection。这样您就不必担心轻量级事务管理器的行为。 IMO 你最好打开自己的连接,并将其传递给两个上下文,并将 contextOwnsConnection 标志设置为 false。

(希望以下都是假设)

不重用连接的后果是可怕的 - 如果 context2 不能 重用与 context1 相同的连接,边界 TransactionScope 会升级为分布式事务,这在任何情况下都会导致进一步的潜在锁定 + 死锁问题。

即在您的示例中,与 context1 中修改后的员工关联的 3 行将被阻止到 context2 上具有 ReadCommited 隔离级别的第二个数据库连接,直到提交事务,或者回滚(即您的程序可能会挂起,直到事务或命令超时)。

我想这是一个亟待解决的问题 - 由于两个上下文都使用相同的数据库,因此如果可能,请尝试将两个上下文结合起来。通过这种方式,您可以完全避免边界 TransactionScope - SaveChanges() 充当针对单个连接的单阶段事务。