Entity Framework con NOLOCK


Risposte:


207

No, ma è possibile avviare una transazione e impostare il livello di isolamento per leggere senza commit . Questo essenzialmente fa lo stesso di NOLOCK, ma invece di farlo in base alla tabella, lo farà per tutto ciò che rientra nell'ambito della transazione.

Se questo suona come quello che vuoi, ecco come potresti fare per farlo ...

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

Eccellente @DoctaJonez È stato introdotto qualcosa di nuovo in EF4 per questo?
FMFF,

@FMFF Non so se sia stato introdotto qualcosa di nuovo per EF4. So che il codice sopra funziona con EFv1 e versioni successive.
Dottore Jones,

quale sarebbe la conseguenza? se qualcuno omette la transazioneScope.Complete () nel blocco sopra menzionato? Pensi che dovrei presentare un'altra domanda per questo?
Eakan Gopalakrishnan,

@EakanGopalakrishnan La mancata chiamata a questo metodo interrompe la transazione, poiché il gestore delle transazioni la interpreta come un errore di sistema o eccezioni introdotte nell'ambito della transazione. (Tratto da MSDN msdn.microsoft.com/en-us/library/… )
Doctor Jones

1
@JsonStatham è stato aggiunto in questa richiesta pull , che è per la pietra miliare 2.1.0
Doctor Jones

83

I metodi di estensione possono semplificarlo

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

L'utilizzo di questo nel mio progetto comporta l'utilizzo completo del pool di connessioni con conseguente eccezione. non riesco a capire perché. Qualcun altro ha questo problema? Eventuali suggerimenti?
Ben Tidman,

1
Nessun problema Ben, non dimenticare di smaltire SEMPRE il tuo contesto di connessione.
Alexandre,

È stato in grado di restringere il problema per escludere l'ambito della transazione come possibile causa. Grazie. Aveva qualcosa a che fare con alcune cose di tentativi di connessione che avevo nel mio costruttore.
Ben Tidman,

Credo che l'ambito dovrebbe essere TransactionScopeOption.Suppress
CodeGrue,

@Alexandre Cosa succederebbe se lo facessi all'interno di un'altra transazione ReadCommitted? Ad esempio ho generato una transazione per iniziare a salvare i dati, ma ora sto interrogando più dati e quindi spawing una transazione non confermata all'interno? Chiamare questo "Completato" completerà anche la mia transazione esterna? Si prega di avvisare :)
Jason Loki Smith il

27

Se hai bisogno di qualcosa in generale, il modo migliore che abbiamo trovato meno invadente rispetto all'avvio effettivo di un ambito di transazione ogni volta, è semplicemente impostare il livello di isolamento della transazione predefinito sulla tua connessione dopo aver creato il contesto dell'oggetto eseguendo questo semplice comando:

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

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

Con questa tecnica, siamo stati in grado di creare un semplice provider EF che crea il contesto per noi ed esegue effettivamente questo comando ogni volta per tutto il nostro contesto in modo da essere sempre "letti non confermati" per impostazione predefinita.


2
L'impostazione del solo livello di isolamento della transazione non avrà alcun effetto. In realtà è necessario eseguire una transazione per avere effetto. La documentazione MSDN per gli stati READ UNCOMMITTED Transactions running at the READ UNCOMMITTED level do not issue shared locks. Ciò implica che è necessario eseguire una transazione per ottenere il vantaggio. (tratto da msdn.microsoft.com/en-gb/library/ms173763.aspx ). Il tuo approccio potrebbe essere meno invadente, ma non otterrà nulla se non utilizzi una transazione.
Dottore Jones,

3
La documentazione MSDN dice: "Controlla il comportamento di blocco e controllo delle versioni delle righe delle istruzioni Transact-SQL emesse da una connessione a SQL Server." e "Specifica che le istruzioni possono leggere le righe che sono state modificate da altre transazioni ma non ancora impegnate." Questa affermazione che ho scritto ha effetto su OGNI affermazione SQL, all'interno o meno di una transazione. Non mi piace contraddire le persone online, ma ti sbagli chiaramente su quello basato sul nostro uso di questa affermazione in un ampio ambiente di produzione. Non dare per scontato le cose, PROVALO!
Frank

Li ho provati, abbiamo un ambiente ad alto carico in cui non eseguire query in uno di questi ambiti di transazione (e una transazione corrispondente) si tradurrà in un deadlock. Le mie osservazioni sono state fatte su un server SQL 2005, quindi non so se il comportamento sia cambiato da allora. Consiglierei quindi questo; se si specifica un livello di isolamento di lettura senza commit ma si continuano a riscontrare deadlock, provare a inserire le query all'interno di una transazione. Se non si verificano deadlock senza creare una transazione, allora abbastanza giusto.
Dottore Jones,

3
@DoctorJones: per quanto riguarda Microsoft SQL Server, tutte le query sono intrinsecamente transazioni. Specificare una transazione esplicita è solo un mezzo per raggruppare 2 o più istruzioni nella stessa transazione in modo che possano essere considerate un'unità di lavoro atomica. Il SET TRANSACTION ISOLATION LEVEL...comando influenza una proprietà a livello di connessione e quindi influenza tutte le istruzioni SQL fatte da quel punto in avanti (per quella connessione), a meno che non vengano sovrascritte da un suggerimento per la query. Questo comportamento esiste da almeno SQL Server 2000 e probabilmente prima.
Solomon Rutzky,

5
@DoctorJones - Scopri: msdn.microsoft.com/en-us/library/ms173763.aspx . Ecco un test. In SSMS, aprire una query (# 1) ed eseguire: CREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);. Aprire un'altra query (# 2) ed eseguire: SELECT * FROM ##Test;. SELECT non verrà restituito poiché viene bloccato dalla transazione ancora aperta nella scheda n. 1 che utilizza un blocco esclusivo. Annulla SELECT in # 2. Esegui SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDuna volta nella scheda # 2. Esegui nuovamente SELEZIONA nella scheda # 2 e tornerà. Assicurati di eseguire ROLLBACKnella scheda # 1.
Solomon Rutzky,

21

Anche se sono assolutamente d'accordo sul fatto che l'utilizzo del livello di isolamento della transazione Read Uncommitted sia la scelta migliore, ma qualche volta hai costretto a utilizzare il suggerimento NOLOCK su richiesta del gestore o del cliente e non sono state accettate motivazioni.

Con Entity Framework 6 è possibile implementare il proprio DbCommandInterceptor in questo modo:

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

Con questa classe attiva, puoi applicarla all'avvio dell'applicazione:

DbInterception.Add(new NoLockInterceptor());

E disattivare condizionalmente l'aggiunta di NOLOCKsuggerimenti nelle query per il thread corrente:

NoLockInterceptor.SuppressNoLock = true;

Mi piace questa soluzione anche se ho modificato leggermente il regex in:
Russ

2
(? <tableAlias>] AS [Extent \ d +] (?! WITH (NOLOCK))) per impedire l'aggiunta di nolock alla tabella derivata che causa un errore. :)
Russ

L'impostazione di SuppressNoLock a livello di thread è un modo conveniente, ma è facile dimenticare di annullare l'impostazione booleana, è necessario utilizzare una funzione che restituisce IDisposable, il metodo Dispose può semplicemente impostare nuovamente il bool su false. Inoltre, ThreadStatic non è realmente compatibile con async / await: stackoverflow.com/questions/13010563/…
Jaap

Oppure, se preferisci usare il LIVELLO DI ISOLAMENTO: 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,

Sta aggiungendo nolock anche alle funzioni del database. Come evitare le funzioni?
Ivan Lewis,

9

Miglioramento della risposta accettata del dottor Jones e utilizzo di PostSharp ;

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

Quindi ogni volta che ne hai bisogno,

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

        }
    }

Essere in grado di aggiungere "NOLOCK" con un intercettore è anche bello, ma non funzionerà quando ci si collega ad altri sistemi di database come Oracle in quanto tale.


6

Per ovviare a questo, creo una vista sul database e applico NOLOCK sulla query della vista. Tratto quindi la vista come una tabella all'interno di EF.


4

Con l'introduzione di EF6, Microsoft consiglia di utilizzare il metodo BeginTransaction ().

È possibile utilizzare BeginTransaction anziché TransactionScope in EF6 + e 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

No, non proprio - Entity Framework è fondamentalmente un livello abbastanza rigoroso sopra il tuo database reale. Le tue query sono formulate in ESQL - Entity SQL - che è prima di tutto mirato al tuo modello di entità e poiché EF supporta più back-end di database, non puoi realmente inviare SQL "nativo" direttamente al tuo back-end.

Il suggerimento per le query NOLOCK è una cosa specifica di SQL Server e non funzionerà su nessuno degli altri database supportati (a meno che non abbiano implementato lo stesso suggerimento - di cui dubito fortemente).

Marc


Questa risposta non è aggiornata: puoi utilizzare NOLOCK come altri hanno già detto ed eseguire SQL "nativo" utilizzando Database.ExecuteSqlCommand()o DbSet<T>.SqlQuery().
Dunc l'

1
@Dunc: grazie per il downvote - btw: NON dovresti usare (NOLOCK)comunque - vedi Cattive abitudini per calciare - mettere NOLOCK ovunque - NON È CONSIGLIATO usare questo ovunque - al contrario!
marc_s

0

Un'opzione è utilizzare una procedura memorizzata (simile alla soluzione di visualizzazione proposta da Ryan) e quindi eseguire la procedura memorizzata da EF. In questo modo la procedura memorizzata esegue la lettura sporca mentre EF invia semplicemente i risultati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.