Entity Framework mit NOLOCK

138

Wie kann ich die NOLOCKFunktion in Entity Framework verwenden? Ist XML der einzige Weg, dies zu tun?

OneSmartGuy
quelle

Antworten:

207

Nein, aber Sie können eine Transaktion starten und die Isolationsstufe so einstellen , dass sie nicht festgeschrieben wird . Dies macht im Wesentlichen dasselbe wie NOLOCK, aber anstatt es pro Tabelle zu tun, wird es für alles im Rahmen der Transaktion getan.

Wenn das so klingt, wie Sie es möchten, können Sie es folgendermaßen tun ...

//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();
}
Doktor Jones
quelle
Ausgezeichnet @DoctaJonez Wurde dafür in EF4 etwas Neues eingeführt?
FMFF
@FMFF Ich weiß nicht, ob für EF4 etwas Neues eingeführt wurde. Ich weiß jedoch, dass der obige Code mit EFv1 und höher funktioniert.
Doktor Jones
Was wäre die Folge? Wenn jemand die transactionScope.Complete () in dem oben genannten Block weglässt? Denkst du, ich sollte noch eine Frage dazu stellen?
Eakan Gopalakrishnan
@EakanGopalakrishnan Wenn diese Methode nicht aufgerufen wird, wird die Transaktion abgebrochen, da der Transaktionsmanager dies als Systemfehler oder als Ausnahmen im Rahmen der Transaktion interpretiert. (Entnommen aus MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones
1
@JsonStatham es wurde in dieser Pull-Anfrage hinzugefügt , die für Meilenstein 2.1.0
Doctor Jones
83

Erweiterungsmethoden können dies erleichtern

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;
    }
}
Alexandre
quelle
Die Verwendung in meinem Projekt führt dazu, dass der Verbindungspool vollständig genutzt wird, was zu Ausnahmen führt. kann nicht herausfinden warum. Hat noch jemand diese Probleme? Irgendwelche Vorschläge?
Ben Tidman
1
Keine Probleme Ben, vergiss nicht, IMMER deinen Verbindungskontext zu entsorgen.
Alexandre
Konnte das Problem eingrenzen, um den Transaktionsumfang als mögliche Ursache auszuschließen. Vielen Dank. Hatte etwas mit einigen Verbindungsversuchen zu tun, die ich in meinem Konstruktor hatte.
Ben Tidman
Ich glaube, der Umfang sollte TransactionScopeOption.Suppress
CodeGrue
@Alexandre Was würde passieren, wenn ich dies in einer anderen ReadCommitted-Transaktion tun würde? Zum Beispiel habe ich eine Transaktion erzeugt, um mit dem Speichern von Daten zu beginnen, aber jetzt frage ich mehr Daten ab und speichere daher eine ReadUncommitted-Transaktion innerhalb von? Wird das Aufrufen von "Vollständig" auch meine äußere Transaktion abschließen? Bitte raten Sie :)
Jason Loki Smith
27

Wenn Sie etwas im Allgemeinen benötigen, ist es am besten, die Standardtransaktionsisolationsstufe für Ihre Verbindung festzulegen, nachdem Sie Ihren Objektkontext erstellt haben, indem Sie diesen einfachen Befehl ausführen:

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

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

Mit dieser Technik konnten wir einen einfachen EF-Anbieter erstellen, der den Kontext für uns erstellt und diesen Befehl jedes Mal für den gesamten Kontext ausführt, sodass wir standardmäßig immer "nicht festgeschrieben" lesen.

Frank.Germain
quelle
2
Das Festlegen der Transaktionsisolationsstufe allein hat keine Auswirkungen. Sie müssen tatsächlich innerhalb einer Transaktion ausgeführt werden, damit sie Auswirkungen hat. Die MSDN-Dokumentation für READ UNCOMMITTED gibt an Transactions running at the READ UNCOMMITTED level do not issue shared locks. Dies bedeutet, dass Sie innerhalb einer Transaktion ausgeführt werden müssen, um den Vorteil zu erhalten. (entnommen aus msdn.microsoft.com/en-gb/library/ms173763.aspx ). Ihr Ansatz ist zwar weniger aufdringlich, bringt jedoch nichts, wenn Sie keine Transaktion verwenden.
Doktor Jones
3
In der MSDN-Dokumentation heißt es: "Steuert das Sperr- und Zeilenversionsverhalten von Transact-SQL-Anweisungen, die von einer Verbindung zu SQL Server ausgegeben werden." und "Gibt an, dass Anweisungen Zeilen lesen können, die von anderen Transaktionen geändert, aber noch nicht festgeschrieben wurden." Diese Anweisung, die ich geschrieben habe, wirkt sich auf JEDE SQL-Anweisungen aus, ob sie sich in einer Transaktion befinden oder nicht. Ich mag es nicht, Menschen online zu widersprechen, aber Sie liegen eindeutig falsch, da wir diese Aussage in einer großen Produktionsumgebung verwenden. Nehmen Sie keine Dinge an, versuchen Sie es!
Frank.Germain
Ich habe sie ausprobiert. Wir haben eine Umgebung mit hoher Auslastung, in der das Nichtausführen von Abfragen innerhalb eines dieser Transaktionsbereiche (und einer übereinstimmenden Transaktion) zu einem Deadlock führt. Meine Beobachtungen wurden auf einem SQL 2005-Server gemacht, daher weiß ich nicht, ob sich das Verhalten seitdem geändert hat. Ich würde dies daher empfehlen. Wenn Sie eine nicht festgeschriebene Isolationsstufe für das Lesen angeben, aber weiterhin Deadlocks auftreten, versuchen Sie, Ihre Abfragen in eine Transaktion aufzunehmen. Wenn Sie keine Deadlocks erleben, ohne eine Transaktion zu erstellen, dann fair genug.
Doktor Jones
3
@DoctorJones - In Bezug auf Microsoft SQL Server sind alle Abfragen von Natur aus Transaktionen. Das Angeben einer expliziten Transaktion ist nur ein Mittel, um zwei oder mehr Anweisungen in derselben Transaktion zu gruppieren , damit sie als atomare Arbeitseinheit betrachtet werden können. Der SET TRANSACTION ISOLATION LEVEL...Befehl wirkt sich auf eine Eigenschaft auf Verbindungsebene aus und wirkt sich daher auf alle SQL-Anweisungen aus, die ab diesem Zeitpunkt (für DIESE Verbindung) ausgeführt werden, sofern sie nicht durch einen Abfragehinweis überschrieben werden. Dieses Verhalten gibt es seit mindestens SQL Server 2000 und wahrscheinlich schon früher.
Solomon Rutzky
5
@DoctorJones - Check out: msdn.microsoft.com/en-us/library/ms173763.aspx . Hier ist ein Test. Öffnen Sie in SSMS eine Abfrage (Nr. 1) und führen Sie Folgendes aus : CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Öffnen Sie eine andere Abfrage (Nr. 2) und führen Sie Folgendes aus : SELECT * FROM ##Test;. Das SELECT wird nicht zurückgegeben, da es durch die noch geöffnete Transaktion in Registerkarte 1 blockiert wird, die eine exklusive Sperre verwendet. Brechen Sie die AUSWAHL in # 2 ab. Führen Sie SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDeinmal in Tab. 2 aus. Führen Sie nur SELECT erneut in Tab. 2 aus, und es wird wieder angezeigt. Stellen Sie sicher, dass Sie ROLLBACKin Registerkarte 1 ausgeführt werden.
Solomon Rutzky
21

Ich stimmte zwar absolut zu, dass die Verwendung der Isolationsstufe für nicht festgeschriebene Transaktionen die beste Wahl ist, aber einige Zeit haben Sie gezwungen, den NOLOCK-Hinweis auf Anfrage des Managers oder Kunden zu verwenden, und es wurden keine Gründe dafür akzeptiert.

Mit Entity Framework 6 können Sie Ihren eigenen DbCommandInterceptor wie folgt implementieren:

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

Mit dieser Klasse können Sie sie beim Start der Anwendung anwenden:

DbInterception.Add(new NoLockInterceptor());

Deaktivieren Sie unter bestimmten Bedingungen das Hinzufügen von NOLOCKHinweisen zu Abfragen für den aktuellen Thread:

NoLockInterceptor.SuppressNoLock = true;
Yuriy Rozhovetskiy
quelle
Ich mag diese Lösung, obwohl ich den regulären Ausdruck leicht geändert habe zu:
Russ
2
(? <tableAlias>] AS [Extent \ d +] (?! WITH (NOLOCK))), um das Hinzufügen von Nolock zur abgeleiteten Tabelle zu verhindern, was einen Fehler verursacht. :)
Russ
Das Festlegen von SuppressNoLock auf Thread-Ebene ist eine bequeme Methode, aber es ist leicht zu vergessen, den Booleschen Wert zu deaktivieren. Sie sollten eine Funktion verwenden, die IDisposable zurückgibt. Die Dispose-Methode kann den Bool einfach wieder auf false setzen. Außerdem ist ThreadStatic nicht wirklich kompatibel mit async / await: stackoverflow.com/questions/13010563/…
Jaap
Oder wenn Sie lieber ISOLATION LEVEL verwenden möchten: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
Es wird auch nolock an Datenbankfunktionen angehängt. Wie vermeide ich Funktionen?
Ivan Lewis
9

Verbesserung der akzeptierten Antwort von Doktor Jones und Verwendung von PostSharp ;

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

Dann, wann immer Sie es brauchen,

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

        }
    }

Die Möglichkeit, "NOLOCK" mit einem Interceptor hinzuzufügen, ist ebenfalls hilfreich, funktioniert jedoch nicht, wenn eine Verbindung zu anderen Datenbanksystemen wie Oracle als solches hergestellt wird.

myuce
quelle
6

Um dies zu umgehen, erstelle ich eine Ansicht in der Datenbank und wende NOLOCK auf die Abfrage der Ansicht an. Ich behandle die Ansicht dann als Tabelle in EF.

Ryan Galloway
quelle
4

Mit der Einführung von EF6 empfiehlt Microsoft die Verwendung der BeginTransaction () -Methode.

Sie können BeginTransaction anstelle von TransactionScope in EF6 + und EF Core verwenden

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
}
Ali
quelle
2

Nein, nicht wirklich - Entity Framework ist im Grunde eine ziemlich strenge Schicht über Ihrer eigentlichen Datenbank. Ihre Abfragen werden in ESQL - Entity SQL - formuliert, das in erster Linie auf Ihr Entitätsmodell ausgerichtet ist. Da EF mehrere Datenbank-Backends unterstützt, können Sie "natives" SQL nicht direkt an Ihr Backend senden.

Der NOLOCK-Abfragehinweis ist eine SQL Server-spezifische Sache und funktioniert in keiner der anderen unterstützten Datenbanken (es sei denn, sie haben denselben Hinweis ebenfalls implementiert - was ich stark bezweifle).

Marc

marc_s
quelle
Diese Antwort ist veraltet - Sie können NOLOCK wie bereits erwähnt verwenden und "natives" SQL mit Database.ExecuteSqlCommand()oder ausführen DbSet<T>.SqlQuery().
Dunc
1
@Dunc: danke für die Abwertung - übrigens: du solltest es sowieso NICHT verwenden (NOLOCK)- siehe Bad Habits to kick - NOLOCK überall einsetzen - es wird NICHT EMPFOHLEN , dies überall zu verwenden - ganz im Gegenteil!
marc_s
0

Eine Möglichkeit besteht darin, eine gespeicherte Prozedur zu verwenden (ähnlich der von Ryan vorgeschlagenen Ansichtslösung) und dann die gespeicherte Prozedur von EF auszuführen. Auf diese Weise führt die gespeicherte Prozedur den Dirty Read durch, während EF nur die Ergebnisse weiterleitet.

Rafiki
quelle