wake-up-neo.net

Get Entity Framework 6 verwendet NOLOCK in den darunter stehenden SELECT-Anweisungen

Ich verwende Entity Framework 6 in einem MVC 5-Projekt. Wie Sie wissen, werden SELECT Abfragen in SQL Server schneller und effizienter ausgeführt, wenn wir WITH (NOLOCK) in ihnen verwenden. Ich habe einige von Entity Framework 6 generierte SQL SELECT-Anweisungen ausgecheckt und festgestellt, dass keine von ihnen NOLOCK enthält.

Ich möchte keine Transaktionen in meinen Abrufoperationen verwenden, um aus nicht festgeschriebenen Transaktionen zu lesen.

Wie kann ich EF 6 erzwingen, NOLOCK in den darunter generierten SELECT-Anweisungen zu verwenden?

33
Arash

Zuallererst ... Sie sollten NIEMALS NOLOCK für jede SQL-Anweisung verwenden. Dies könnte die Integrität Ihrer Daten gefährden.

Es ist wie jede andere Abfrage ein Hinweis auf einen Mechanismus, den Sie nur verwenden sollten, wenn Sie etwas Ungewöhnliches tun.

Es gibt keine Möglichkeit, den EF-Anbieter anzuweisen, den NoLock-Hinweis zu rendern. Wenn Sie wirklich nicht festgeschriebene Daten lesen müssen, haben Sie die folgende Option.

  1. Schreiben Sie Ihren eigenen EntityFramework-Provider.

  2. Verwenden Sie einen Command Interceptor, um die Anweisung zu ändern, bevor sie ausgeführt wird. http://msdn.Microsoft.com/en-us/data/dn469464.aspx

  3. Verwenden Sie ein TransactionScope mit IsolationLevel.ReadUncommited.

Ich weiß, dass Sie gesagt haben, dass Sie keine Transaktionen verwenden möchten, aber dies ist die einzige sofort einsatzbereite Möglichkeit, nicht festgeschriebene Daten zu lesen. Außerdem verursacht es nicht viel Overhead, da jede Anweisung in SQL Server "implizit" in einer Transaktion ausgeführt wird.

using (new TransactionScope(
                    TransactionScopeOption.Required, 
                    new TransactionOptions 
                    { 
                         IsolationLevel = IsolationLevel.ReadUncommitted 
                    })) 
{
        using (var db = new MyDbContext()) { 
            // query
        }
}

BEARBEITEN: Es ist auch wichtig zu beachten, dass NOLOCK für Updates und Löschvorgänge (Auswahl bleibt erhalten) von Microsoft ab SQL Server 2016 veraltet ist und sein wird entfernt in einer zukünftigen Version.

https://docs.Microsoft.com/en-us/sql/database-engine/deprecated-database-engine-features-in-sql-server-2016?view=sql-server-2017

49
codeworx

Sie können eine Problemumgehung verwenden, die keine Transaktionsbereiche für jede Abfrage verwendet. Wenn Sie den folgenden Code ausführen, verwendet ef dieselbe Transaktionsisolationsstufe für dieselbe Serverprozess-ID. Da sich die Server-Prozess-ID nicht in derselben Anforderung ändert, ist nur ein Aufruf für jede Anforderung ausreichend. Dies funktioniert auch in EF Core.

this.Database.ExecuteSqlCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
9
Cem Mutlu

Ich bin damit einverstanden, was Codeworx so sagt, dass "Read Uncommitted" gefährlich sein kann. Wenn Sie mit Dirty Reads leben können, machen Sie es.

Ich habe eine Möglichkeit gefunden, diese Funktion zu nutzen, ohne die aktuellen Abfragen zu ändern.

Sie müssen einen DbCommandInterceptor wie folgt erstellen:

public class IsolationLevelInterceptor : DbCommandInterceptor
{
    private IsolationLevel _isolationLevel;

    public IsolationLevelInterceptor(IsolationLevel level)
    {
        _isolationLevel = level;
    }



    //[ThreadStatic]
    //private DbCommand _command;


    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        SetTransaction(command);

    }

    public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        SetTransaction(command);
    }

    public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        SetTransaction(command);
    }




    private void SetTransaction(DbCommand command)
    {
        if (command != null)
        {
            if (command.Transaction == null)
            {
                var t = command.Connection.BeginTransaction(_isolationLevel);
                command.Transaction = t;
                //_command = command;
            }
        }
    }

}

fügen Sie dann am cctor (statischer Konstruktor Ihres dbcontext) den Interceptor zur DbInfrastructure von Entity Framework-Auflistungen hinzu.

DbInterception.Add(new IsolationLevelInterceptor());

dadurch wird für jeden Befehl, den EF an den Store sendet, eine Transaktion mit dieser Isolationsstufe umgebrochen.

In meinem Fall hat es gut funktioniert, da wir Daten über eine API schreiben, bei der diese Daten nicht auf den Messwerten aus der Datenbank basieren. (Daten können wegen der Dirty Reads verfälscht werden) und so gut funktioniert.

9
anotherNeo

In unserem Projekt verwenden wir eine Kombination der zweiten und dritten Lösung, die von @Cem Mutlu und @anotherNeo vorgeschlagen wurde.

Das Experimentieren mit SQL Profiler hat gezeigt, dass wir zwei Befehle verwenden müssen:

  • LESEN SIE UNVERBINDLICH
  • LESEN SIE ENGAGIERT

weil NET Verbindungen über SqlConnectionPool wiederverwendet

internal class NoLockInterceptor : DbCommandInterceptor
{
    public static readonly string SET_READ_UNCOMMITED = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
    public static readonly string SET_READ_COMMITED = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";

    public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        if (!interceptionContext.DbContexts.OfType<IFortisDataStoreNoLockContext>().Any())
        {
            return;
        }

        ExecutingBase(command);
    }

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        if (!interceptionContext.DbContexts.OfType<IFortisDataStoreNoLockContext>().Any())
        {
            return;
        }

        ExecutingBase(command);
    }

    private static void ExecutingBase(DbCommand command)
    {
        var text = command.CommandText;
        command.CommandText = $"{SET_READ_UNCOMMITED} {Environment.NewLine} {text} {Environment.NewLine} {SET_READ_COMMITED}";
    }
}
2
StuS