web-dev-qa-db-fra.com

Entity Framework avec NOLOCK

Comment utiliser la fonction NOLOCK sur Entity Framework? XML est-il le seul moyen de le faire?

131
OneSmartGuy

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 relève de la transaction.

Si cela ressemble à ce que vous voulez, voici comment vous pouvez le faire ...

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

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

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

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 cette commande à chaque fois pour l'ensemble de notre contexte, de sorte que nous sommes toujours en "lecture non validée" par défaut.

26
Frank.Germain

Bien que je sois tout à fait d’accord pour dire que l’utilisation du niveau d’isolation de transaction Lecture non validée est le meilleur choix, vous avez parfois été forcé d’utiliser NOLOCK hint à la demande du responsable ou du client, sans qu'aucune raison ne soit 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 éventuellement l'ajout de l'indicateur NOLOCK dans les requêtes pour le fil actuel: 

NoLockInterceptor.SuppressNoLock = true;
19

Enrichissement sur la réponse acceptée de Doctor 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
            //...
            //...

        }
    }

Pouvoir 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 tels qu'Oracle en tant que tels.

9
myuce

Pour résoudre ce problème, je crée une vue sur la base de données et applique NOLOCK à la requête de la vue. Je traite ensuite la vue comme une table dans EF. 

6
Ryan Galloway

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 s’adresse tout d’abord à votre modèle d’entité. Etant donné que EF prend en charge plusieurs bases de données, vous ne pouvez pas réellement envoyer de code SQL "natif" directement à votre système.

L'indicateur de requête NOLOCK est spécifique à SQL Server et ne fonctionnera pas avec les autres bases de données prises en charge (à moins qu'elles aient également implémenté le même indicateur, ce dont je doute fort).

Marc

2
marc_s

Avec l'introduction de 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
}
2
Ali

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 modifiée alors que EF ne fait que diriger les résultats.

0
Rafiki