Entity Framework avec NOLOCK

138

Comment puis-je utiliser la NOLOCKfonction sur Entity Framework? XML est-il le seul moyen de le faire?

OneSmartGuy
la source

Réponses:

207

Non, mais vous pouvez démarrer une transaction et définir le niveau d'isolation sur lecture non validée . Cela fait essentiellement la même chose que NOLOCK, mais au lieu de le faire par table, il le fera pour tout ce qui est dans le cadre de la transaction.

Si cela ressemble à ce que vous voulez, voici comment procéder ...

//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();
}
Docteur Jones
la source
Excellent @DoctaJonez Est-ce que quelque chose de nouveau a été introduit dans EF4 pour cela?
FMFF
@FMFF Je ne sais pas si quelque chose de nouveau a été introduit pour EF4. Je sais que le code ci-dessus fonctionne avec EFv1 et plus.
Doctor Jones
quelle en serait la conséquence? si quelqu'un omet le transactionScope.Complete () dans le bloc mentionné ci-dessus? Pensez-vous que je devrais déposer une autre question à ce sujet?
Eakan Gopalakrishnan
@EakanGopalakrishnan Ne pas appeler cette méthode annule la transaction, car le gestionnaire de transactions l'interprète comme une défaillance du système ou des exceptions levées dans le cadre de la transaction. (Tiré de MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones
1
@JsonStatham, il a été ajouté à cette pull request , qui concerne le jalon 2.1.0
Doctor Jones
83

Les méthodes d'extension peuvent rendre cela plus facile

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
la source
L'utilisation de cela dans mon projet entraîne une utilisation complète du pool de connexions, ce qui entraîne une exception. ne peut pas comprendre pourquoi. Quelqu'un d'autre a ce problème? Aucune suggestion?
Ben Tidman
1
Aucun problème Ben, n'oubliez pas de TOUJOURS disposer votre contexte de connexion.
Alexandre
A été en mesure de limiter le problème pour exclure la portée de la transaction comme cause possible. Merci. Avait quelque chose à voir avec certaines tentatives de connexion que j'avais dans mon constructeur.
Ben Tidman
Je pense que la portée devrait être TransactionScopeOption.Suppress
CodeGrue
@Alexandre Que se passerait-il si je faisais cela dans une autre transaction ReadCommitted? Par exemple, j'ai engendré une transaction pour commencer à enregistrer des données, mais maintenant j'interroge plus de données et donc génère une transaction ReadUncommitted dans? Est-ce que le fait d'appeler cela "Complet" complétera également ma transaction externe? Veuillez conseiller :)
Jason Loki Smith
27

Si vous avez besoin de quelque chose en général, la meilleure façon dont nous avons trouvé qui est moins intrusif que de démarrer réellement une transactioncope à chaque fois, est de simplement définir le niveau d'isolation de transaction par défaut sur votre connexion après avoir créé votre contexte d'objet en exécutant cette simple commande:

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

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

Avec cette technique, nous avons pu créer un fournisseur EF simple qui crée le contexte pour nous et exécute réellement cette commande à chaque fois pour l'ensemble de notre contexte afin que nous soyons toujours en «lecture non validée» par défaut.

Frank.Germain
la source
2
La définition du seul niveau d'isolement des transactions n'aura aucun effet. Vous devez en fait être en cours d'exécution dans une transaction pour qu'elle ait un effet. La documentation MSDN pour les états READ UNCOMMITTED Transactions running at the READ UNCOMMITTED level do not issue shared locks. Cela implique que vous devez exécuter une transaction pour en tirer profit. (extrait de msdn.microsoft.com/en-gb/library/ms173763.aspx ). Votre approche est peut-être moins intrusive, mais elle n'apportera rien si vous n'utilisez pas de transaction.
Doctor Jones
3
La documentation MSDN indique: «Contrôle le comportement de verrouillage et de gestion des versions de ligne des instructions Transact-SQL émises par une connexion à SQL Server». et "Spécifie que les instructions peuvent lire les lignes qui ont été modifiées par d'autres transactions mais pas encore validées." Cette déclaration que j'ai écrite affecte TOUTES les instructions SQL, qu'elle soit à l'intérieur d'une transaction ou non. Je n'aime pas contredire les gens en ligne, mais vous vous trompez clairement sur celui-là basé sur notre utilisation de cette déclaration dans un grand environnement de production. Ne présumez pas les choses, essayez-les!
Frank.Germain
Je les ai essayés, nous avons un environnement à charge élevée dans lequel ne pas effectuer de requêtes dans l'une de ces portées de transaction (et une transaction correspondante) entraînera un blocage. Mes observations ont été faites sur un serveur SQL 2005, donc je ne sais pas si le comportement a changé depuis. Je recommande donc ceci; si vous spécifiez un niveau d'isolation en lecture non validée mais que vous continuez à rencontrer des blocages, essayez de placer vos requêtes dans une transaction. Si vous ne rencontrez pas de blocages sans créer de transaction, alors c'est juste.
Doctor Jones
3
@DoctorJones - en ce qui concerne Microsoft SQL Server, toutes les requêtes sont par nature des transactions. Spécifier une transaction explicite est juste un moyen de regrouper 2 ou plusieurs instructions dans la même transaction afin qu'elles puissent être considérées comme une unité de travail atomique. La SET TRANSACTION ISOLATION LEVEL...commande affecte une propriété au niveau de la connexion et par conséquent affecte toutes les instructions SQL effectuées à partir de ce point (pour CETTE connexion), sauf si elle est remplacée par une indication de requête. Ce comportement existe depuis au moins SQL Server 2000 et probablement avant.
Solomon Rutzky
5
@DoctorJones - Consultez: msdn.microsoft.com/en-us/library/ms173763.aspx . Voici un test. Dans SSMS, ouvrez une requête (n ° 1) et exécutez: CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Ouvrez une autre requête (n ° 2) et exécutez: SELECT * FROM ##Test;. Le SELECT ne reviendra pas car il est bloqué par la transaction toujours ouverte dans l'onglet n ° 1 qui utilise un verrou exclusif. Annulez le SELECT dans # 2. Exécutez SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDune fois dans l'onglet # 2. Exécutez juste le SELECT à nouveau dans l'onglet # 2 et il reviendra. Assurez-vous d'exécuter ROLLBACKdans l'onglet # 1.
Solomon Rutzky
21

Bien que je sois tout à fait d'accord pour dire que l'utilisation du niveau d'isolation de transaction Read Uncommitted est le meilleur choix, mais vous avez parfois été obligé d'utiliser l'indication NOLOCK à la demande du gestionnaire ou du client et aucune raison ne l'a acceptée.

Avec Entity Framework 6, vous pouvez implémenter votre propre DbCommandInterceptor comme ceci:

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

Avec cette classe en place, vous pouvez l'appliquer au démarrage de l'application:

DbInterception.Add(new NoLockInterceptor());

Et désactivez conditionnellement l'ajout d' NOLOCKindices dans les requêtes pour le thread actuel:

NoLockInterceptor.SuppressNoLock = true;
Yuriy Rozhovetskiy
la source
J'aime cette solution même si j'ai légèrement modifié l'expression régulière en:
Russ
2
(? <tableAlias>] AS [Extent \ d +] (?! WITH (NOLOCK))) pour empêcher l'ajout de nolock à la table dérivée qui provoque une erreur. :)
Russ
Définir SuppressNoLock au niveau du thread est un moyen pratique, mais il est facile d'oublier de désactiver le booléen, vous devez utiliser une fonction qui retourne IDisposable, la méthode Dispose peut simplement redéfinir le bool sur false. De plus, ThreadStatic n'est pas vraiment compatible avec async / await: stackoverflow.com/questions/13010563/…
Jaap
Ou, si vous préférez utiliser le NIVEAU D'ISOLATION: 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
Il ajoute également nolock aux fonctions de base de données. Comment éviter les fonctions?
Ivan Lewis
9

Amélioration de la réponse acceptée par le docteur Jones et utilisation de PostSharp ;

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

Puis chaque fois que vous en avez besoin,

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

        }
    }

Être capable d'ajouter "NOLOCK" avec un intercepteur est également agréable mais ne fonctionnera pas lors de la connexion à d'autres systèmes de base de données comme Oracle en tant que tel.

myuce
la source
6

Pour contourner cela, je crée une vue sur la base de données et j'applique NOLOCK sur la requête de la vue. Je traite ensuite la vue comme une table dans EF.

Ryan Galloway
la source
4

Avec l'introduction d'EF6, Microsoft recommande d'utiliser la méthode BeginTransaction ().

Vous pouvez utiliser BeginTransaction au lieu de TransactionScope dans EF6 + et 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
}
Ali
la source
2

Non, pas vraiment - Entity Framework est fondamentalement une couche assez stricte au-dessus de votre base de données réelle. Vos requêtes sont formulées dans ESQL - Entity SQL - qui est tout d'abord ciblé vers votre modèle d'entité, et comme EF prend en charge plusieurs backends de base de données, vous ne pouvez pas vraiment envoyer du SQL "natif" directement à votre backend.

L'indicateur de requête NOLOCK est une chose spécifique à SQL Server et ne fonctionnera sur aucune des autres bases de données prises en charge (à moins qu'elles aient également implémenté le même indice - ce dont je doute fortement).

Marc

marc_s
la source
Cette réponse est obsolète - vous pouvez utiliser NOLOCK comme d'autres l'ont mentionné, et vous pouvez exécuter du SQL "natif" en utilisant Database.ExecuteSqlCommand()ou DbSet<T>.SqlQuery().
Dunc
1
@Dunc: merci pour le vote négatif - btw: vous ne devriez PAS utiliser de (NOLOCK)toute façon - voir Bad Habits to kick - mettre NOLOCK partout - il n'est PAS RECOMMANDÉ de l'utiliser partout - bien au contraire!
marc_s
0

Une option consiste à utiliser une procédure stockée (similaire à la solution d'affichage proposée par Ryan), puis à exécuter la procédure stockée à partir d'EF. De cette façon, la procédure stockée effectue la lecture incorrecte tandis que EF canalise uniquement les résultats.

Rafiki
la source