为什么Entity Framework忽略TransactionScope(不添加NOLOCK)?

Yov*_*vav 3 entity-framework nolock transactionscope entity-framework-6

我错过了什么?

我正在尝试使用像这样的TransactionScope读取NOLOCK:

var scopeOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted };
using (var scope = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
   using (var db = new MyDbContext(ConnectionStringEntities))
   {
      // Simple read with a try catch block...
   }
   scope.Complete();
}
Run Code Online (Sandbox Code Playgroud)

我希望看到NOLOCK添加到SQL查询中(查看SQL Profiler以及自定义DbCommandInterceptor - 但它不存在...

更新:经过一些更多的研究,我想知道所选的光标是否正在被使用,只是没有NOLOCK"提示"(特定于SQL Server - 并且还特定于一个表),我找到了一些代码来获取当前事务和它似乎显示正确选择的事务隔离(ReadUncommitted/Serializable等)我仍然想测试它,但如果你有任何想法让我知道

获取当前的.net TransactionScope IsolationLevel

Transaction trans = Transaction.Current;
System.Transactions.IsolationLevel level = trans.IsolationLevel;
LogService.Instance.Debug($"Transaction IsolationLevel = {level.ToString()}");
Run Code Online (Sandbox Code Playgroud)

Yov*_*vav 5

所以看起来Entity Framework确实尊重IsolationLevel,只是它没有使用NOLOCK提示(可能是因为它太具有数据库特性),这就是我对EF的主要抱怨 - 它不是针对不同的数据库类型进行了非常优化,另一个例子是新身份将AspNetUsers的GUID主键保存为字符串(再次缺乏优化)除此之外(以及其他一些事情)EF非常棒!

我无法在任何地方找到我的问题的解决方案,我绝对不想让我的所有查询都使用NOLOCK - 只是未提交的,所以我最终结合了两个解决方案(有一些更改):

  1. NoLockInterceptor - 用于动态添加NOLOCK(带有NOLOCK的实体框架):

    /// <summary>
    /// Add "WITH (NOLOCK)" hint to SQL queries, SQL Server specifc - may break queries on different databases.
    /// (conditionally turn off with NoLockInterceptor.AddNoLockHintToSqlQueries = false to change on runtime)
    /// <para>
    /// https://stackoverflow.com/questions/926656/entity-framework-with-nolock
    /// </para>
    /// </summary>
    public class NoLockInterceptor : DbCommandInterceptor
    {
        private static readonly Regex TableAliasRegex = new Regex(
            @"(?<tableAlias>AS \[Extent\d+\](?! WITH \(NOLOCK\)))",
            RegexOptions.Multiline | RegexOptions.IgnoreCase);
    
        /// <summary>
        /// Add "WITH (NOLOCK)" hint to SQL queries - unique to each thread 
        /// (set to true only when needed and then back to false)
        /// </summary>
        [ThreadStatic]
        public static bool AddNoLockHintToSqlQueries;
    
        public NoLockInterceptor()
        {
            // Do not use by default for all queries
            AddNoLockHintToSqlQueries = false;
        }
    
        public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            if (AddNoLockHintToSqlQueries)
            {
                command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
            }
        }
    
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            if (AddNoLockHintToSqlQueries)
            {
                command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. TransactionWrapper - 调用NoLockInterceptor行为,对重复使用事务也很有用(http://haacked.com/archive/2009/08/18/simpler-transactions.aspx/):

    /// <summary>
    /// Transaction wrapper for setting pre-defined transaction scopes
    /// <para>
    /// http://haacked.com/archive/2009/08/18/simpler-transactions.aspx/
    /// </para>
    /// </summary>
    public static class TransactionWrapper
    {
        /// <summary>
        /// Set transaction scope and using NoLockInterceptor for adding SQL Server specific "WITH (NOLOCK)" 
        /// to ReadUncommitted isolation level transactions (not supported by Entity Framework)
        /// </summary>
        /// <param name="isolationLevel"></param>
        /// <param name="transactionScopeOption"></param>
        /// <param name="timeout"></param>
        /// <param name="action"></param>
        public static void SetScope(IsolationLevel isolationLevel, TransactionScopeOption transactionScopeOption,
            TimeSpan timeout, Action action)
        {
            var transactionOptions = new TransactionOptions { IsolationLevel = isolationLevel, Timeout = timeout };
            using (var transactionScope = new TransactionScope(transactionScopeOption, transactionOptions))
            {
                if (isolationLevel == IsolationLevel.ReadUncommitted)
                    NoLockInterceptor.AddNoLockHintToSqlQueries = true;
    
                action();
                transactionScope.Complete();
    
                if (isolationLevel == IsolationLevel.ReadUncommitted)
                    NoLockInterceptor.AddNoLockHintToSqlQueries = false;
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

像这样使用它:

var timeout = TimeSpan.FromSeconds(ConfigVariables.Instance.Timeout_Transaction_Default_In_Seconds);
TransactionWrapper.SetScope(IsolationLevel.ReadUncommitted, TransactionScopeOption.Required, timeout, () =>
{
    using (var db = new MyDbContext(MyDbContextConnectionStringEntities))
    {
       // Do stuff...
    }
});
Run Code Online (Sandbox Code Playgroud)

现在,NOLOCK仅添加到具有ReadUncommitted事务隔离级别范围的查询中.