Entity Framework з NOLOCK


Відповіді:


207

Ні, але ви можете розпочати транзакцію і встановити рівень ізоляції, щоб читати її не можна . Це по суті робить те саме, що і 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 і вище, хоча.
Доктор Джонс

який би був наслідок? якщо хтось ухиляється відрокactionScope.Complete () у згаданому вище блоці? Як ви вважаєте, я повинен поставити ще одне питання для цього?
Eakan Gopalakrishnan

@EakanGopalakrishnan Невдача викликати цей метод припиняє транзакцію, оскільки менеджер транзакцій трактує це як системний збій або винятки, викинуті в межах транзакції. (Взято з MSDN msdn.microsoft.com/en-us/library/… )
Доктор Джонс

1
@JsonStatham це було додано до цього запиту на витяг , який є віхою 2.1.0
Доктор Джонс

83

Методи розширення можуть полегшити це

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
Немає питань, Бен, не забувай ЗАВЖДИ розпоряджатися своїм контекстом зв'язку.
Олександр

Вдалося звузити проблему, щоб виключити обсяг транзакції як можливу причину. Дякую. Щось стосувалося якихось речей, що намагаються повторити, я мав у своєму конструкторі.
Бен Тідман

Я вважаю, що сфера повинна бути TransactionScopeOption.Suppress
CodeGrue

@Alexandre Що буде, якщо я зробив це в рамках іншої трансакції ReadCommitted? Наприклад, я породив транзакцію, щоб почати зберігати дані, але тепер я запитую більше даних і, отже, вкладаю транзакцію ReadUncommitted в межах? Чи виклик цього "завершеного" також завершить мою зовнішню транзакцію? Прошу порадити :)
Джейсон Локі Сміт,

27

Якщо вам потрібно щось взагалі, найкращий спосіб, який ми виявили, що менш нав'язливий, ніж щоразу починати транзакцію, - це просто встановити рівень ізоляції транзакцій за вашим з'єднанням після того, як ви створили свій об'єктний контекст, виконавши цю просту команду:

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

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

За допомогою цієї методики нам вдалося створити простий провайдер EF, який створює для нас контекст і фактично виконує цю команду кожного разу для всього нашого контексту, щоб ми за замовчуванням завжди знаходились у режимі "читання не передано".


2
Встановлення рівня ізоляції транзакцій не матиме жодного ефекту. Для того, щоб мати будь-який ефект, вам потрібно працювати в рамках транзакції. Документація MSDN для "ЧИТАТИ НЕЗАКОМЕНДЕНО" 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 , зроблені з цього моменту (для даного з'єднання), якщо вони НЕ були перезаписані натяком запиту. Така поведінка існувала як мінімум з 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. Запустіть просто ВИБІР знову на вкладці №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;

Мені подобається це рішення, хоча я злегка змінив регулярний вираз на:
Russ

2
(? <tableAlias>] AS [Обсяг \ d +] (?! З (НОЛОК))), щоб запобігти додаванню нолока до отриманої таблиці, що спричиняє помилку. :)
Russ

Налаштування SuppressNoLock на рівні потоку - це зручний спосіб, але легко забути зняти булеву функцію, вам слід скористатися функцією, яка повертає IDisposable, метод Dispose може просто встановити bool на значення false знову. Крім того , ThreadStatic не надто сумісні з асинхронним / ОЖИДАНИЕ: stackoverflow.com/questions/13010563 / ...
Яап

Або, якщо ви хочете скористатися ІЗОЛАЦІЙНИМ РІВНЯМ: 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); }
Аді

Він додає замок і до функцій бази даних. Як уникнути функцій?
Іван Льюїс

9

Вдосконалення прийнятої відповіді доктора Джонса та використання 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 як такої.


6

Щоб обійти це питання, я створюю перегляд бази даних і застосовую 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, і вона не працюватиме на будь-якій іншій підтримуваній базі даних (якщо тільки вони також не реалізували ту саму підказку - в чому я сильно сумніваюся).

Марк


Ця відповідь застаріла - ви можете використовувати NOLOCK, як згадували інші, а "рідний" SQL можна виконати за допомогою Database.ExecuteSqlCommand()або DbSet<T>.SqlQuery().
Данк

1
@Dunc: спасибі за downvote - до речі: ви повинні НЕ використовувати в (NOLOCK)будь-якому випадку - див Шкідливі звички до гри - покласти NOLOCK всюди - це НЕ рекомендується використовувати це всюди - зовсім навпаки!
marc_s

0

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.