Почему Entity Framework игнорирует TransactionScope (не добавляя с помощью NOLOCK)?

Что мне не хватает?

Я пытаюсь читать с NOLOCK, используя TransactionScope следующим образом:

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 и т. д.) Я все еще хочу протестировать его, но дайте мне знать, если у вас есть какие-либо мысли

Получить текущий уровень изоляции TransactionScope .net

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

person Yovav    schedule 03.04.2016    source источник


Ответы (3)


Итак, похоже, что Entity Framework уважает IsolationLevel, только он не использует подсказку NOLOCK (вероятно, потому, что она слишком специфична для базы данных), и это, кстати, моя основная жалоба на EF - она ​​не очень оптимизирована для разных типов баз данных, другой пример, когда новое удостоверение сохраняет первичный ключ GUID для AspNetUsers в виде строки (опять же из-за отсутствия оптимизации), кроме этого (и некоторых других вещей) EF - это потрясающе!

Я нигде не мог найти решение своей проблемы, я определенно не хотел, чтобы все мои запросы использовали NOLOCK - только незафиксированные, поэтому в итоге я объединил два решения (с некоторыми изменениями):

  1. NoLockInterceptor - для добавления NOLOCK на лету (Entity Framework с 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.

person Yovav    schedule 04.04.2016

Вы не можете заставить Entity Framework отображать подсказку NOLOCK. Если вы хотите прочитать незафиксированные данные, вам нужно сделать что-то другое, как вы сделали, добавив TransactionScope с IsolationLevel.ReadUncommited к TransactionOptions.

Также подойдет написание собственного командного перехватчика или собственного поставщика EF.

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

person William Xifaras    schedule 03.04.2016
comment
TransactionScope работал с предыдущими версиями EF (около 4 лет назад), знаете ли вы, изменилось ли это в EF6 или, может быть, это ошибка? - person Yovav; 03.04.2016
comment
Он по-прежнему работает, но есть новые API, которые также могут обрабатывать транзакции, такие как DbContext.Database.BeginTransaction и DbContext.Database.UseTransaction. - person William Xifaras; 03.04.2016

Я пробовал область транзакции, а затем профилировал вызовы в БД. EF начинает и завершает транзакцию, но никогда не изменяет уровень изоляции с Read Committed.

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