Entity Framework z NOLOCK


Odpowiedzi:


207

Nie, ale możesz rozpocząć transakcję i ustawić poziom odseparowania na odczyt niezatwierdzonych . Zasadniczo robi to to samo co NOLOCK, ale zamiast robić to na podstawie tabeli, zrobi to dla wszystkiego w zakresie transakcji.

Jeśli to brzmi tak, jak chcesz, oto jak możesz to zrobić ...

//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();
}

Doskonały @DoctaJonez Czy w EF4 wprowadzono coś nowego?
FMFF,

@FMFF Nie wiem, czy coś nowego zostało wprowadzone dla EF4. Wiem jednak, że powyższy kod działa z EFv1 i nowszymi wersjami.
Doctor Jones,

jakie byłyby konsekwencje? jeśli ktoś pominie transakcję transactionScope.Complete () w powyższym bloku? Czy uważasz, że powinienem zadać inne pytanie w tej sprawie?
Eakan Gopalakrishnan

@EakanGopalakrishnan Brak wywołania tej metody powoduje przerwanie transakcji, ponieważ menedżer transakcji interpretuje to jako awarię systemu lub wyjątki zgłoszone w zakresie transakcji. (Zaczerpnięte z MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones,

1
@JsonStatham został dodany w tym żądaniu ściągnięcia , które dotyczy kamienia milowego 2.1.0
Doctor Jones

83

Metody rozszerzające mogą to ułatwić

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

Użycie tego w moim projekcie powoduje całkowite wykorzystanie puli połączeń, co powoduje wyjątek. nie mogę zrozumieć, dlaczego. Czy ktoś jeszcze ma te problemy? Jakieś sugestie?
Ben Tidman

1
Żadnych problemów Ben, nie zapomnij ZAWSZE pozbyć się kontekstu połączenia.
Alexandre,

Udało mi się zawęzić problem, aby wykluczyć zakres transakcji jako możliwą przyczynę. Dzięki. Miało to coś wspólnego z kilkoma próbami ponownego połączenia, które miałem w moim konstruktorze.
Ben Tidman,

Uważam, że zakres powinien obejmować TransactionScopeOption.Suppress
CodeGrue

@Alexandre Co by się stało, gdybym to zrobił w ramach innej transakcji ReadCommitted? Na przykład utworzyłem transakcję, aby rozpocząć zapisywanie danych, ale teraz odpytuję o więcej danych, a zatem tworzę transakcję ReadUncommitted? Czy nazwanie tej opcji „Zakończono” również zakończy moją zewnętrzną transakcję? Uprzejmie radzę :)
Jason Loki Smith

27

Jeśli potrzebujesz czegoś ogólnie, najlepszym sposobem, jaki znaleźliśmy, który jest mniej uciążliwy niż faktyczne rozpoczynanie zakresu transakcji za każdym razem, jest po prostu ustawienie domyślnego poziomu izolacji transakcji w połączeniu po utworzeniu kontekstu obiektu, uruchamiając to proste polecenie:

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

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

Dzięki tej technice byliśmy w stanie stworzyć prostego dostawcę EF, który tworzy dla nas kontekst i faktycznie uruchamia to polecenie za każdym razem dla całego naszego kontekstu, dzięki czemu zawsze jesteśmy w stanie „czytaj niezatwierdzone”.


2
Samo ustawienie poziomu izolacji transakcji nie będzie miało żadnego wpływu. Aby transakcja miała jakikolwiek efekt, musisz działać w ramach transakcji. Dokumentacja MSDN dotycząca stanów READ UNCOMMITTED Transactions running at the READ UNCOMMITTED level do not issue shared locks. Oznacza to, że musisz działać w ramach transakcji, aby uzyskać korzyść. (pobrane z msdn.microsoft.com/en-gb/library/ms173763.aspx ). Twoje podejście może być mniej inwazyjne, ale nic nie da, jeśli nie wykorzystasz transakcji.
Doctor Jones,

3
Dokumentacja MSDN mówi: „Kontroluje blokowanie i przechowywanie wersji wierszy instrukcji języka Transact-SQL wydanych przez połączenie z serwerem SQL”. i „Określa, że ​​instrukcje mogą odczytywać wiersze, które zostały zmodyfikowane przez inne transakcje, ale jeszcze nie zostały zatwierdzone”. Ta instrukcja, którą napisałem, wpływa na KAŻDĄ instrukcję SQL, niezależnie od tego, czy jest zawarta w transakcji, czy nie. Nie lubię zaprzeczać ludziom online, ale wyraźnie się mylisz, biorąc pod uwagę to, że używamy tego stwierdzenia w dużym środowisku produkcyjnym. Nie zakładaj, WYPRÓBUJ ICH!
Frank.Germain

Wypróbowałem je, mamy środowisko o dużym obciążeniu, w którym niewykonanie zapytań w jednym z tych zakresów transakcji (i pasującej transakcji) spowoduje zakleszczenie. Moje obserwacje zostały wykonane na serwerze SQL 2005, więc nie wiem, czy od tego czasu zachowanie się zmieniło. Dlatego poleciłbym to; Jeśli określisz niezatwierdzony poziom izolacji odczytu, ale nadal występują zakleszczenia, spróbuj umieścić zapytania w ramach transakcji. Jeśli nie wystąpią zakleszczenia bez tworzenia transakcji, to wystarczy.
Doctor Jones

3
@DoctorJones - w odniesieniu do Microsoft SQL Server wszystkie zapytania są z natury transakcjami. Określenie jawnej transakcji jest po prostu sposobem na zgrupowanie 2 lub więcej instrukcji w tej samej transakcji, aby można je było uznać za niepodzielną jednostkę pracy. SET TRANSACTION ISOLATION LEVEL...Komenda wpływa na właściwość Połączenie poziomie, a tym samym wpływa na wszystkie instrukcje SQL wykonane z tego punktu do przodu (do tego połączenia), chyba że zostaną zamienione przez nutą zapytania. To zachowanie występuje od co najmniej SQL Server 2000 i prawdopodobnie wcześniej.
Solomon Rutzky

5
@DoctorJones - sprawdź: msdn.microsoft.com/en-us/library/ms173763.aspx . Oto test. W SSMS, otwórz kwerendę (nr 1) i wykonaj: CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Otworzyć kolejne zapytanie (# 2) i wykonaj: SELECT * FROM ##Test;. SELECT nie powróci, ponieważ jest blokowany przez wciąż otwartą transakcję na karcie 1, która używa blokady na wyłączność. Anuluj WYBÓR w # 2. Uruchom SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDraz w zakładce # 2. Uruchom ponownie SELECT w zakładce # 2 i wróci. Pamiętaj, aby uruchomić ROLLBACKw zakładce nr 1.
Solomon Rutzky

21

Chociaż absolutnie zgodziłem się, że użycie poziomu izolacji transakcji Odczyt niezatwierdzonych jest najlepszym wyborem, ale od jakiegoś czasu zmuszony byłeś użyć wskazówki NOLOCK na prośbę menedżera lub klienta i nie zaakceptowano żadnych powodów przeciwko temu.

Za pomocą Entity Framework 6 można zaimplementować własne DbCommandInterceptor w następujący sposób:

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

Mając tę ​​klasę, możesz ją zastosować na początku aplikacji:

DbInterception.Add(new NoLockInterceptor());

I warunkowo wyłącz dodawanie NOLOCKpodpowiedzi do zapytań dotyczących bieżącego wątku:

NoLockInterceptor.SuppressNoLock = true;

Podoba mi się to rozwiązanie, chociaż nieznacznie zmieniłem wyrażenie regularne na:
Russ,

2
(? <tableAlias>] AS [Extent \ d +] (?! WITH (NOLOCK))), aby zapobiec dodaniu nolocka do tabeli pochodnej, co powoduje błąd. :)
Russ

Ustawienie SuppressNoLock na poziomie wątku jest wygodnym sposobem, ale łatwo zapomnieć o usunięciu wartości logicznej, należy użyć funkcji, która zwraca IDisposable, metoda Dispose może po prostu ponownie ustawić wartość bool na false. Ponadto ThreadStatic nie jest tak naprawdę kompatybilny z async / await: stackoverflow.com/questions/13010563/ ...
Jaap

Lub, jeśli wolisz użyć POZIOMU ​​IZOLACJI: 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); }
Adi

Dodaje również nolock do funkcji bazy danych. Jak unikać funkcji?
Ivan Lewis

9

Ulepszanie zaakceptowanej odpowiedzi Doktora Jonesa i używanie PostSharp ;

Pierwszy „ 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();
            }
        }
    }
}

Wtedy kiedy będziesz tego potrzebować,

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

        }
    }

Możliwość dodania „NOLOCK” z przechwytywaczem jest również przyjemna, ale nie będzie działać podczas łączenia się z innymi systemami baz danych, takimi jak Oracle.


6

Aby to obejść, tworzę widok w bazie danych i stosuję NOLOCK w zapytaniu widoku. Następnie traktuję widok jako tabelę w EF.


4

Wraz z wprowadzeniem EF6 firma Microsoft zaleca używanie metody BeginTransaction ().

Możesz użyć BeginTransaction zamiast TransactionScope w EF6 + i 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

Nie, nie do końca - Entity Framework jest w zasadzie dość ścisłą warstwą nad rzeczywistą bazą danych. Twoje zapytania są formułowane w ESQL - Entity SQL - który jest przede wszystkim ukierunkowany na Twój model encji, a ponieważ EF obsługuje wiele backendów baz danych, nie możesz tak naprawdę wysyłać „natywnego” SQL bezpośrednio do swojego zaplecza.

Wskazówka dotycząca zapytania NOLOCK jest specyficzna dla SQL Server i nie będzie działać na żadnej z innych obsługiwanych baz danych (chyba że zaimplementowały one również tę samą wskazówkę - w co mocno wątpię).

Marc


Ta odpowiedź jest nieaktualna - możesz używać NOLOCK, jak wspominali inni, i możesz wykonywać „natywny” SQL za pomocą Database.ExecuteSqlCommand()lub DbSet<T>.SqlQuery().
Dunc

1
@Dunc: dzięki za głosy przeciwne - przy okazji: i tak NIE powinieneś używać (NOLOCK)- zobacz Złe nawyki do kopania - umieszczanie NOLOCK wszędzie - NIE ZALECA SIĘ używać tego wszędzie - wręcz przeciwnie!
marc_s

0

Jedną z opcji jest użycie procedury składowanej (podobnej do rozwiązania widoku zaproponowanego przez Ryana), a następnie wykonanie procedury składowanej z EF. W ten sposób procedura składowana wykonuje brudny odczyt, podczas gdy EF po prostu przesyła wyniki.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.