如何全局更改所有Entity Framework事务的隔离级别

7

我正在评估EF(实体框架)是否适用于我的下一个新应用程序。

如何全局更改应用程序中所有EF事务的隔离级别? 例如:假设我想使用“读取提交的快照”。

虽然在需要TransactionScope时指定IsolationLevel是可以的(请参见下面的代码),但是将每个EF保存操作封装在TransactionScope中会很丑陋。

 'OK
    Using tsc As New TransactionScope(TransactionScopeOption.RequiresNew, TransactionOption.ReadCommitted)
        UpdateShoppingCart
        EnqueueNewOrder
        SendConfirmationEmail
        tsc.Complete
    End Using

    'Is this really the only way to avoid Serializable?
    Using tsc As New TransactionScope(TransactionScopeOption.RequiresNew, TransactionOption.ReadCommitted)
      _ctx.SaveChanges()
      tsc.Complete
    End Using

    Class TransactionOption
        Public Shared ReadOnly ReadCommitted As New TransactionOptions() With {
            .IsolationLevel = IsolationLevel.ReadCommitted,
            .Timeout = TransactionManager.DefaultTimeout
            }
    End Class

我认为混合使用隔离级别不是一个好主意。这种做法有什么问题吗?

使用Serializable和SQL Server(与Oracle相比)插入一个简单的读取可能会导致转换锁定死锁。

根据EF FAQ: "建议使用READ COMMITTED事务,并在需要读者不阻止写者且写者不阻止读者时使用READ COMMITTED SNAPSHOT ISOLATION."

我不理解为什么EF默认使用Serializable,并且使更改默认隔离级别变得如此困难 - 与SQL Server(与Oracle的多版本控制相比)默认使用悲观并发模型有关。配置选项应该非常容易实现 - 或者我错过了什么重要信息?


2
你在哪里找到EF默认为Serializable的信息? - Ladislav Mrnka
2个回答

9
我相信EF的默认事务隔离级别是基于所使用的数据库提供程序的。 SaveChanges 执行以下代码:
    ... 
    try
    {
        this.EnsureConnection();
        flag = true;
        Transaction current = Transaction.Current;
        bool flag2 = false;
        if (connection.CurrentTransaction == null)
        {
            flag2 = null == this._lastTransaction;
        }
        using (DbTransaction transaction = null)
        {
            if (flag2)
            {
                transaction = connection.BeginTransaction();
            }
            objectStateEntriesCount = this._adapter.Update(this.ObjectStateManager);
            if (transaction != null)
            {
                transaction.Commit();
            }
        }
    }
    ...

如您所见,BeginTransaction 被调用时没有指定 IsolationLevel。在幕后,它使用特定于提供程序的事务创建了 IsolationLevel.Unspecified。未指定隔离级别应该会导致数据库服务器/驱动程序的默认隔离级别。在 SQL Server 中,默认隔离级别是 READ COMMITED,因此我预计它会使用它,但我尚未测试。

如果您想全局更改隔离级别,可以在派生自 ObjectContext 的类中覆盖 SaveChanges,并将 base.SaveChanges() 包装在自定义的 TransactionScope 中。


你是正确的,我的假设是错误的。也许我在测试时遇到了以下问题:http://connect.microsoft.com/SQLServer/feedback/details/243527/sp-reset-connection-doesnt-reset-isolation-level 我通过首先执行SqlConnection.ClearAllPools()并使用SQLProfiler进行检查来验证EF与SQL Server的默认设置为“读取提交”。 - Peter Meinl

0

在VB中可能存在限制,但在C#中,你会这样做:

 TransactionOptions transactionOptions = GetTransactionOptions();
 new TransactionScope(TransactionScopeOption.RequiresNew, transactionOptions) 

请注意第二个参数是transactionOptions,可以按以下方式创建:
 public TransactionOptions GetTransactionOptions()
    {
        TransactionOptions transactionOptions = new TransactionOptions();
        transactionOptions.IsolationLevel = IsolationLevel.ReadCommitted;
        return transactionOptions;
    }

通过将上述代码放入工厂方法中,并在需要事务选项时调用该工厂方法,您可以拥有一个地方来更改解决方案中所有事务范围。

1
这正是我在我的问题中发布的代码所做的事情 :-) 我正在寻找一种方法,以避免将每个.SaveChanges都包含在TransactionScope中,以将EF的默认Serializable更改为Read Committed快照。 - Peter Meinl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接