【问题标题】:Why does Entity Framework ignore TransactionScope (not adding with NOLOCK)?为什么实体框架会忽略 TransactionScope(不使用 NOLOCK 添加)?
【发布时间】:2016-04-03 14:40:06
【问题描述】:

我错过了什么?

我正在尝试使用这样的 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();
}

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

更新:经过更多研究,我想知道是否最终使用了选定的游标,只是没有 NOLOCK“提示”(特定于 SQL Server - 也特定于一个表),我发现了一些获取当前事务的代码,它似乎显示了正确选择的事务隔离(ReadUncommitted / Serializable 等)我仍然想测试它,但如果您有任何想法,请告诉我

Get current .net TransactionScope IsolationLevel

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

【问题讨论】:

    标签: entity-framework entity-framework-6 transactionscope nolock


    【解决方案1】:

    所以看起来实体框架确实尊重 IsolationLevel,只是它不使用 NOLOCK 提示(可能是因为它太特定于数据库),顺便说一下,我对 EF 的主要抱怨 - 它没有针对不同的数据库类型,另一个例子是新身份将 AspNetUsers 的 GUID 主键保存为字符串(再次因为缺乏优化),除此之外(以及其他一些事情)EF 很棒!

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

    1. NoLockInterceptor - 用于即时添加 NOLOCK (Entity Framework with 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)");
              }
          }
      }
      
    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;
              }
          }
      }
      

    像这样使用它:

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

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

    【讨论】:

      【解决方案2】:

      您无法让实体框架呈现 NOLOCK 提示。如果你想读取未提交的数据,你必须做一些不同的事情,比如将带有 IsolationLevel.ReadUncommited 的 TransactionScope 添加到 TransactionOptions。

      编写您自己的命令拦截器或您自己的 EF 提供程序也可以。

      https://msdn.microsoft.com/en-us/data/dn469464.aspx

      【讨论】:

      • TransactionScope 曾经与 EF 的早期版本(大约 4 年前)一起使用,您知道这在 EF6 中是否发生了变化或者可能是一个错误?
      • 它仍然有效,但也有新的 API 可以处理事务,例如 DbContext.Database.BeginTransaction 和 DbContext.Database.UseTransaction。
      【解决方案3】:

      我已经尝试过事务范围,然后它会分析对数据库的调用。 EF 开始和结束事务,但从不更改已提交读的隔离级别。

                  using (var scope = new TransactionScope(
                  TransactionScopeOption.Required,
                  new TransactionOptions()
                  {
                      IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted
                  }))
              {
                  List<T> toReturn = query.ToList();
                  scope.Complete();
                  return toReturn;
              }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多