21

Sto cercando un modo per eseguire una query mentre TransactionScope è vivo, e ignoro TransactionScope: in pratica, voglio eseguire questa particolare query indipendentemente da cosa.Ignora TransactionScope per query specifiche

Sto utilizzando il codice EF, e il modo in cui l'applicazione è progettata, un nuovo contesto di dati viene aperto più volte in una singola chiamata, ognuno con le proprie modifiche e tutte queste sono contenute in un singolo TransactionScope , che ha chiamato Complete() alla fine, senza problemi. All'interno del contesto abbiamo sovrascritto lo SaveChanges in modo che se si verifica un'eccezione su base.SaveChanges(), possiamo prenderlo e accedere al database prima di eseguire il rollback della transazione.

Poiché il SaveChanges si verifica all'interno della transazione, la registrazione ovviamente non si verifica, perché appartiene alla stessa transazione della chiamata originale. Sto cercando di ignorare completamente TransactionScope solo per il codice di registrazione.

Ecco po 'di codice stripped-down:

// From the context 
public override int SaveChanges() { 
    try { 
     return base.SaveChanges(); 
    } catch (Exception ex) { 

     // Writes to the log table - I want this to run no matter what 
     LogRepo.Log(/*stuff to log from the context*/); 

     throw; 
    } 
} 

// Inside the business logic 
public void DoSomething() { 
    try { 
     using (var scope = new TransactionScope()) { 

      using (var context = new FooContext()) { 
       // Do something 
       context.SaveChanges(); 
      } 
      using (var context = new FooContext()) { 
       // Do something else 
       context.SaveChanges(); 
      } 

      scope.Complete(); 
     } 
    } catch (Exception ex) { 
     // scope.Complete is never called, so the transaction is rolled back 
    } 
} 

Ho provato ad utilizzare ADO.NET regolare invece di EF per la registrazione, ma ancora gli stessi risultati - si ottiene il rollback troppo.

Ho bisogno che la gestione degli errori si verifichi all'interno di SaveChanges, perché quello che sto registrando è lo stato delle entità che vengono salvate - quindi non posso semplicemente spostare facilmente la registrazione da qualche altra parte. Potrei costruire il messaggio all'interno dello SaveChanges catch, e lanciarlo e lasciare che sia DoSomething catch loggarlo, ma ci sono dozzine di metodi DoSomething, e preferirei semplicemente occuparmene in un posto.

risposta

29

Se si avvolge la chiamata di registro all'interno di un altro ambito di transazione con l'opzione di eliminazione attivata, l'ambito della transazione non verrà utilizzato.

public override int SaveChanges() { 
try { 
    return base.SaveChanges(); 
} catch (Exception ex) { 

    string message = /*stuff to log from the context*/; 
    using (var scope = new TransactionScope(TransactionScopeOption.Suppress)) 
    { 
     LogRepo.Log(msg); 

    } 

    throw; 
} 

}

+0

Perfetto! Esattamente quello che stavo cercando. Testato e funziona sia in ADO.NET regolare che in Entity Framework Grazie –

+0

Appena implementato questo e ora ottengo questo errore: L'accesso di rete per Distributed Transaction Manager (MSDTC) è stato disabilitato. Abilitare DTC per l'accesso alla rete nella configurazione di sicurezza per MSDTC utilizzando lo strumento di amministrazione dei componenti. più transazioni possono causare questo errore? –

+0

Questo mi ha salvato un sacco di mal di testa! Sto elaborando i messaggi MSMQ all'interno di una transazione, ma devo raggiungere SQL Azure per un po 'di dati extra e non supportano DTC. ha permesso alle mie transazioni di funzionare ancora, pur lasciando funzionare anche la funzione di Azure. – RubyHaus

1

Solo il mio pensiero iniziale, ma è necessario mettere il LogRepo sul proprio DataContext (DC2) in modo che il TransactionScope circostante (con DC1) non lo arrotoli indietro quando non è impegnato.

Fondamentalmente, è necessario rendere la registrazione autosufficiente e atomica.

EDIT Nel guardarlo ancora un po ', mi sembra che se hai spostato il tuo logout da SaveChanges nel catch() su DoSomething(), la registrazione funzionerebbe. Ma il tuo logging deve ancora essere autonomo e atomico.

+0

Il TransactionScope si avvolge intorno ad ogni contesto dati che è dentro di esso - il LogRepo fa creare il proprio marchio nuovo contesto, ma è ancora raccolto dal TransactionScope. –

+0

Per la tua modifica - ho già detto - devo almeno creare il messaggio di errore dall'interno di "SaveChanges" perché ho bisogno di accedere al contesto di dati aperti, ma anche se costruisco il messaggio da lì e lo lascio al chiamante, 'Devo ancora eseguire la registrazione nel chiamante diverse dozzine di volte, e sto cercando di mantenere tutto in un unico posto. –

+1

Sì, è chiaro che la tua implementazione non funzionerà come previsto.L'altra opzione sarebbe quella di prendere il tuo LogRepo e renderlo un singleton, creato all'avvio dell'app, con il proprio DBContext creato al di fuori di tutti gli ambiti di transazione. Non è il design più pulito, ma ti porterà in giro con questo business TransactionScope. Non ho ancora trovato un modo per "rimuovere" un contesto da TransactionScope per ora ... –

1

Ho trovato una soluzione di cui non sono veramente soddisfatto, ma sembra funzionare. Apparentemente TransactionScope influisce solo sul thread corrente, quindi la registrazione usando un nuovo thread sembra funzionare correttamente.

public override int SaveChanges() { 
    try { 
     return base.SaveChanges(); 
    } catch (Exception ex) { 

     string message = /*stuff to log from the context*/; 
     new Thread(msg => {  

      LogRepo.Log(msg); 

     }).Start(message); 

     throw; 
    } 
} 
+1

Questo ha sicuramente un sentimento "icky", ma se funziona, funziona. Un po 'come il suggerimento singleton che ho avuto. Non mi piace neanche, ma sicuramente avrebbe funzionato. –

+0

@JoeBrunscheon +1 per "icky" :) –

+0

Va bene quando eseguo il debug. ma in modalità di rilascio o in esecuzione non funziona :( – mostafa8026

Problemi correlati