web-dev-qa-db-fra.com

Get Entity Framework 6 utilise NOLOCK dans ses instructions SELECT en dessous

J'utilise Entity Framework 6 dans un projet MVC 5. Comme vous le savez, les requêtes SELECT dans SQL Server sont plus rapides et plus efficaces si nous y utilisons WITH (NOLOCK). J'ai vérifié quelques instructions SQL SELECT générées par Entity Framework 6 et j'ai réalisé qu'aucune d'entre elles ne contient NOLOCK.

Je ne veux pas utiliser de transactions dans mes opérations de récupération pour lire des transactions non validées.

Comment puis-je appliquer EF 6 pour utiliser NOLOCK dans les instructions SELECT générées ci-dessous?

33
Arash

Tout d'abord ... Vous ne devez JAMAIS utiliser NOLOCK pour chaque instruction SQL. Cela pourrait compromettre l'intégrité de vos données.

C’est comme toute autre requête suggérant un mécanisme que vous ne devez utiliser que lorsque vous faites quelque chose qui sort de l’ordinaire.

Il n'y a aucun moyen de dire au fournisseur EF de restituer l'indice NoLock. Si vous avez vraiment besoin de lire des données non validées, vous avez l'option suivante.

  1. Écrivez votre propre fournisseur EntityFramework.

  2. Utilisez un intercepteur de commandes pour modifier l'instruction avant son exécution. http://msdn.Microsoft.com/en-us/data/dn469464.aspx

  3. Utilisez un TransactionScope avec IsolationLevel.ReadUncommited.

Je sais que vous avez dit que vous ne vouliez pas utiliser Transactions, mais c'est le seul moyen prêt à l'emploi de lire des données non validées. De plus, cela ne produit pas beaucoup de surcharge car chaque instruction dans SQL Server s'exécute "implicitement" dans une transaction.

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

EDIT: Il est important de noter également que NOLOCK pour les mises à jour et les suppressions (les sélections restent intactes) a été déconseillé par Microsoft à partir de SQL Server 2016 et sera supprimé dans "une" future version.

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

49
codeworx

Vous pouvez utiliser une solution de contournement qui n'utilise pas les étendues de transaction pour chaque requête. Si vous exécutez le code ci-dessous, ef utilisera le même niveau d'isolement des transactions pour le même ID de processus serveur. Étant donné que l'ID de processus serveur ne change pas dans la même demande, un seul appel pour chaque demande suffit. Cela fonctionne également dans EF Core.

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

Je suis d'accord avec ce que codeworx dit d'une manière que Read Uncommitted peut être dangereux. Si vous pouvez vivre avec des lectures sales, allez-y.

J'ai trouvé un moyen de faire ce travail sans rien changer dans les requêtes actuelles.

Vous devez créer un DbCommandInterceptor comme ceci:

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

}

puis au niveau du cctor (constructeur statique de votre dbcontext), ajoutez simplement l'intercepteur à la DbInfrastructure des collections de framework d'entité.

DbInterception.Add(new IsolationLevelInterceptor());

ce sera pour chaque commande que EF envoie au magasin, encapsule une transaction avec ce niveau d'isolement.

Dans mon cas, cela a bien fonctionné, car nous écrivons des données via une API où ces données ne sont pas basées sur les lectures de la base de données. (les données peuvent être corrompues à cause des lectures sales) et ont donc bien fonctionné.

9
anotherNeo

Dans notre projet, nous utilisons une combinaison des deuxième et troisième solutions, suggérées par @Cem Mutlu et @anotherNeo.

L'expérience avec Sql Profiler a montré que nous devons utiliser une paire de commandes:

  • LIRE NON ENGAGÉ
  • LIRE ENGAGÉ

car NET réutilise les connexions via SqlConnectionPool

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