Entity Framework с NOLOCK

140

Как я могу использовать NOLOCKфункцию в Entity Framework? XML - единственный способ сделать это?

OneSmartGuy
источник

Ответы:

209

Нет, но вы можете начать транзакцию и установить уровень изоляции на чтение без фиксации . По сути, это то же самое, что и NOLOCK, но вместо того, чтобы делать это для каждой таблицы, он будет делать это для всего в рамках транзакции.

Если это похоже на то, что вы хотите, вот как вы могли бы это сделать ...

//declare the transaction options
var transactionOptions = new System.Transactions.TransactionOptions();
//set it to read uncommited
transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
//create the transaction scope, passing our options in
using (var transactionScope = new System.Transactions.TransactionScope(
    System.Transactions.TransactionScopeOption.Required, 
    transactionOptions)
)

//declare our context
using (var context = new MyEntityConnection())
{
    //any reads we do here will also read uncomitted data
    //...
    //...
    //don't forget to complete the transaction scope
    transactionScope.Complete();
}
Доктор Джонс
источник
Отлично @DoctaJonez Было ли что-то новое в EF4 для этого?
FMFF
@FMFF Не знаю, было ли что-то новое в EF4. Я знаю, что приведенный выше код работает с EFv1 и выше.
Доктор Джонс
каковы будут последствия? если кто-то опускает transactionScope.Complete () в блоке, упомянутом выше? Как вы думаете, мне следует задать еще один вопрос по этому поводу?
Eakan Gopalakrishnan
@EakanGopalakrishnan. Невозможность вызова этого метода приводит к прерыванию транзакции, поскольку диспетчер транзакций интерпретирует это как системный сбой или исключения, возникшие в рамках транзакции. (Взято из MSDN msdn.microsoft.com/en-us/library/… )
Доктор Джонс,
1
@JsonStatham, он был добавлен в этот запрос на перенос , который относится к этапу 2.1.0
Доктор Джонс,
86

Методы расширения могут сделать это проще

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

public static int CountReadUncommitted<T>(this IQueryable<T> query)
{
    using (var scope = new TransactionScope(
        TransactionScopeOption.Required, 
        new TransactionOptions() { 
            IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
    {
        int toReturn = query.Count();
        scope.Complete();
        return toReturn;
    }
}
Александр
источник
Использование этого в моем проекте приводит к тому, что пул соединений полностью используется, что приводит к исключению. не могу понять почему. У кого-нибудь еще есть эти проблемы? Какие-либо предложения?
Бен Тидман
1
Нет проблем, Бен, не забывай ВСЕГДА избавляться от контекста подключения.
Alexandre
Смог сузить проблему, чтобы исключить область транзакции как возможную причину. Спасибо. Имел какое-то отношение к некоторым повторениям подключения, которые у меня были в моем конструкторе.
Бен Тидман
Я считаю, что область действия должна быть TransactionScopeOption.Suppress
CodeGrue
@Alexandre Что бы произошло, если бы я сделал это в другой транзакции ReadCommitted? Например, я создал транзакцию, чтобы начать сохранение данных, но теперь я запрашиваю больше данных и, следовательно, порожу транзакцию ReadUncommitted внутри? Завершит ли вызов этого «Завершенного» мою внешнюю транзакцию? Любезный совет :)
Джейсон Локи Смит
28

Если вам нужно что-то в целом, лучший способ, который, как мы обнаружили, менее навязчивый, чем фактический запуск области транзакции каждый раз, - это просто установить уровень изоляции транзакции по умолчанию для вашего соединения после того, как вы создали контекст объекта, выполнив эту простую команду:

this.context.ExecuteStoreCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");

http://msdn.microsoft.com/en-us/library/aa259216(v=sql.80).aspx

С помощью этого метода мы смогли создать простой поставщик EF, который создает для нас контекст и фактически запускает эту команду каждый раз для всего нашего контекста, так что по умолчанию мы всегда находимся в состоянии «чтение не зафиксировано».

Фрэнк Жермэн
источник
2
Сама по себе установка уровня изоляции транзакции не даст никакого эффекта. На самом деле вам нужно работать внутри транзакции, чтобы она имела какой-либо эффект. В документации MSDN для READ UNCOMMITTED указано Transactions running at the READ UNCOMMITTED level do not issue shared locks. Это означает, что вы должны работать в рамках транзакции, чтобы получить выгоду. (взято из msdn.microsoft.com/en-gb/library/ms173763.aspx ). Ваш подход может быть менее навязчивым, но он ничего не даст, если вы не используете транзакцию.
Доктор Джонс
3
В документации MSDN сказано: «Управляет блокировкой и управлением версиями строк для операторов Transact-SQL, выдаваемых подключением к SQL Server». и «Указывает, что операторы могут читать строки, которые были изменены другими транзакциями, но еще не зафиксированы». Этот оператор, который я написал, влияет на ВСЕ операторы SQL, независимо от того, находится он внутри транзакции или нет. Я не люблю противоречить людям в Интернете, но вы явно ошибаетесь в этом, поскольку мы используем это утверждение в большой производственной среде. Не предполагайте ничего, ПОПРОБУЙТЕ ИХ!
Frank.Germain
Я пробовал их, у нас есть среда с высокой нагрузкой, где невыполнение запросов в одной из этих областей транзакции (и соответствующей транзакции) приведет к тупиковой ситуации. Мои наблюдения были сделаны на сервере SQL 2005, поэтому я не знаю, изменилось ли с тех пор поведение. Поэтому я рекомендую это; если вы указываете уровень изоляции незафиксированного чтения, но продолжаете испытывать взаимоблокировки, попробуйте поместить свои запросы в транзакцию. Если вы не сталкиваетесь с тупиками без создания транзакции, то этого достаточно.
Доктор Джонс
3
@DoctorJones - что касается Microsoft SQL Server, все запросы по сути являются транзакциями. Указание явной транзакции - это просто средство сгруппировать 2 или более операторов в одну транзакцию, чтобы их можно было рассматривать как атомарную единицу работы. Команда SET TRANSACTION ISOLATION LEVEL...влияет на свойство уровня соединения и, следовательно, влияет на все операторы SQL, сделанные с этой точки (для соединения THAT), если только это не переопределено подсказкой запроса. Такое поведение существует, по крайней мере, с SQL Server 2000 и, вероятно, раньше.
Соломон Рутцки
5
@DoctorJones - Проверьте: msdn.microsoft.com/en-us/library/ms173763.aspx . Вот тест. В SSMS, откройте запрос (# 1) и пробег: CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Откройте другой запрос (# 2) и запустить: SELECT * FROM ##Test;. SELECT не вернется, поскольку он блокируется все еще открытой транзакцией на вкладке №1, которая использует эксклюзивную блокировку. Отмените выбор в # 2. Выполните SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDодин раз на вкладке №2. Снова запустите только SELECT на вкладке № 2, и она вернется. Обязательно запустите ROLLBACKвкладку №1.
Соломон Руцки
21

Хотя я абсолютно согласен с тем, что использование уровня изоляции Read Uncommitted транзакции - лучший выбор, но некоторое время вы заставляли использовать подсказку NOLOCK по запросу менеджера или клиента, и никаких причин против этого не принималось.

С Entity Framework 6 вы можете реализовать собственный DbCommandInterceptor следующим образом:

public class NoLockInterceptor : DbCommandInterceptor
{
    private static readonly Regex _tableAliasRegex = 
        new Regex(@"(?<tableAlias>AS \[Extent\d+\](?! WITH \(NOLOCK\)))", 
            RegexOptions.Multiline | RegexOptions.IgnoreCase);

    [ThreadStatic]
    public static bool SuppressNoLock;

    public override void ScalarExecuting(DbCommand command, 
        DbCommandInterceptionContext<object> interceptionContext)
    {
        if (!SuppressNoLock)
        {
            command.CommandText = 
                _tableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
        }
    }

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        if (!SuppressNoLock)
        {
            command.CommandText = 
                _tableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
        }
    }
}

Имея этот класс, вы можете применить его при запуске приложения:

DbInterception.Add(new NoLockInterceptor());

И условно отключите добавление NOLOCKподсказки в запросы для текущего потока:

NoLockInterceptor.SuppressNoLock = true;
Юрий Рожовецкий
источник
Мне нравится это решение, хотя я немного изменил регулярное выражение на:
Расс
2
(? <tableAlias>] AS [Extent \ d +] (?! WITH (NOLOCK))), чтобы предотвратить добавление nolock в производную таблицу, что приводит к ошибке. :)
Russ
Установка SuppressNoLock на уровне потока - удобный способ, но легко забыть отключить логическое значение, вы должны использовать функцию, которая возвращает IDisposable, метод Dispose может просто снова установить для bool значение false. Кроме того, ThreadStatic не совсем совместим с async / await: stackoverflow.com/questions/13010563/…
Jaap
Или, если вы предпочитаете использовать УРОВЕНЬ ИЗОЛЯЦИИ: public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if (!SuppressNoLock) command.CommandText = $"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;{Environment.NewLine}{command.CommandText}"; base.ReaderExecuting(command, interceptionContext); }
Ади
Он также добавляет блокировку к функциям базы данных. Как избежать для функций?
Иван Льюис
10

Улучшение принятого ответа доктора Джонса и использование PostSharp ;

Во- первых « ReadUncommitedTransactionScopeAttribute »

[Serializable]
public class ReadUncommitedTransactionScopeAttribute : MethodInterceptionAspect
{
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        //declare the transaction options
        var transactionOptions = new TransactionOptions();
        //set it to read uncommited
        transactionOptions.IsolationLevel = IsolationLevel.ReadUncommitted;
        //create the transaction scope, passing our options in
        using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
        {
            //declare our context
            using (var scope = new TransactionScope())
            {
                args.Proceed();
                scope.Complete();
            }
        }
    }
}

Тогда, когда вам это понадобится,

    [ReadUncommitedTransactionScope()]
    public static SomeEntities[] GetSomeEntities()
    {
        using (var context = new MyEntityConnection())
        {
            //any reads we do here will also read uncomitted data
            //...
            //...

        }
    }

Возможность добавить «NOLOCK» с перехватчиком тоже хорошо, но не будет работать при подключении к другим системам баз данных, таким как Oracle как таковые.

Myuce
источник
7

Чтобы обойти это, я создаю представление в базе данных и применяю NOLOCK к запросу представления. Затем я рассматриваю представление как таблицу в EF.

Райан Гэллоуэй
источник
4

С введением EF6 Microsoft рекомендует использовать метод BeginTransaction ().

Вы можете использовать BeginTransaction вместо TransactionScope в EF6 + и EF Core

using (var ctx = new ContractDbContext())
using (var transaction = ctx.Database.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted))
{
    //any reads we do here will also read uncommitted data
}
Али
источник
2

Нет, не совсем - Entity Framework - это, по сути, довольно строгий уровень над вашей реальной базой данных. Ваши запросы формулируются на ESQL - Entity SQL - который в первую очередь ориентирован на вашу модель сущностей, а поскольку EF поддерживает несколько бэкэндов базы данных, вы действительно не можете отправлять «родной» SQL прямо в бэкэнд.

Подсказка запроса NOLOCK специфична для SQL Server и не будет работать ни с одной из других поддерживаемых баз данных (если они также не реализовали ту же подсказку - в чем я сильно сомневаюсь).

Марк

marc_s
источник
Этот ответ устарел - вы можете использовать NOLOCK, как упоминали другие, и вы можете выполнять «собственный» SQL с помощью Database.ExecuteSqlCommand()или DbSet<T>.SqlQuery().
Дунк
1
@Dunc: спасибо за отрицательный голос - кстати: вы все равно НЕ должны использовать (NOLOCK)- см. Плохие привычки, чтобы пнуть - повсюду ставить NOLOCK - НЕ РЕКОМЕНДУЕТСЯ использовать его везде - совсем наоборот!
marc_s
0

Один из вариантов - использовать хранимую процедуру (аналогичную представлению, предложенному Райаном), а затем выполнить хранимую процедуру из EF. Таким образом, хранимая процедура выполняет грязное чтение, а EF просто передает результаты.

Рафики
источник