Utilizzando Transactions o SaveChanges (false) e AcceptAllChanges ()?


346

Sono stato verifica delle operazioni e sembra che si prendono cura di se stessi in EF finché mi passa falseper SaveChanges()e quindi chiamare AcceptAllChanges()se non ci sono errori:

SaveChanges(false);
// ...
AcceptAllChanges();

E se qualcosa va male? non devo eseguire il rollback o, non appena il mio metodo esce dal campo di applicazione, la transazione è terminata?

Cosa succede a tutte le colonne indipendenti che sono state assegnate a metà della transazione? Presumo che se qualcun altro ha aggiunto un record dopo il mio prima che il mio andasse male, questo significa che ci sarà un valore di identità mancante.

C'è qualche motivo per usare la TransactionScopeclasse standard nel mio codice?


1
Questo mi ha aiutato a capire perché SaveChanges(fase); ... AcceptAllChanges();era uno schema in primo luogo. Notare come la risposta accettata alla domanda sopra, è scritta dall'autore di un blog e a quel blog viene fatto riferimento nell'altra domanda. Tutto viene insieme.
The Red Pea,

Risposte:


451

Con Entity Framework la maggior parte del tempo SaveChanges()è sufficiente. Questo crea una transazione o si arruola in qualsiasi transazione ambientale e fa tutto il lavoro necessario in quella transazione.

A volte però l' SaveChanges(false) + AcceptAllChanges()abbinamento è utile.

Il posto più utile per questo è nelle situazioni in cui si desidera eseguire una transazione distribuita tra due diversi contesti.

Vale a dire qualcosa del genere (male):

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save and discard changes
    context1.SaveChanges();

    //Save and discard changes
    context2.SaveChanges();

    //if we get here things are looking good.
    scope.Complete();
}

Se context1.SaveChanges()riesce ma context2.SaveChanges()fallisce l'intera transazione distribuita viene interrotta. Ma sfortunatamente Entity Framework ha già scartato le modifiche context1, quindi non è possibile riprodurre o registrare efficacemente l'errore.

Ma se modifichi il codice in questo modo:

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save Changes but don't discard yet
    context1.SaveChanges(false);

    //Save Changes but don't discard yet
    context2.SaveChanges(false);

    //if we get here things are looking good.
    scope.Complete();
    context1.AcceptAllChanges();
    context2.AcceptAllChanges();

}

Mentre la chiamata a SaveChanges(false)invia i comandi necessari al database, il contesto stesso non viene modificato, quindi è possibile farlo di nuovo se necessario, oppure è possibile interrogare ObjectStateManagerse lo si desidera.

Ciò significa che se la transazione genera effettivamente un'eccezione che è possibile compensare, riprovando o registrando lo stato di ciascun contesto ObjectStateManagerda qualche parte.

Vedi il mio post sul blog per ulteriori informazioni.


3
Ottimo, grazie ... Quindi se qualcosa non riesce non devo tornare indietro ?? SaveChanges, lo contrassegna per essere salvato, ma in realtà non si impegna fino a quando non accetto tutti i cambiamenti .. ma se qualcosa va storto .. dovrò eseguire il rollback, quindi il mio oggetto tornerà al suo stato corretto?
Mark Smith

33
@Mark: se per "rollback" intendi, riportare i tuoi oggetti allo stato in cui si trovano nel database, quindi no, non vorrai farlo perché perderesti tutte le modifiche dell'utente agli oggetti . SaveChanges(false)esegue l'effettivo aggiornamento del database, mentre AcceptAllChanges()dice a EF: "Okay, puoi dimenticare quali cose devono essere salvate, perché sono state salvate con successo." Se SaveChanges(false)fallisce, non AcceptAllChanges()verrà mai chiamato e EF considererà ancora l'oggetto come proprietà che è stata modificata e deve essere salvata nel database.
BlueRaja - Danny Pflughoeft

Potete consigliarvi come fare usando prima il codice? Non esiste alcun parametro per il metodo SaveChanges o AcceptAllChanges
Kirsten Greed,

2
Ho fatto una domanda sull'uso di questa tecnica con Code First qui
Kirsten Greed,

13
Questo non è più possibile in EF 6.1. Sai che tipo di modifiche devono essere apportate per funzionare ora?
Alex Dresko,

113

Se si utilizza EF6 (Entity Framework 6+), questo è cambiato per le chiamate del database a SQL.
Vedi: http://msdn.microsoft.com/en-us/data/dn456843.aspx

usa context.Database.BeginTransaction.

Da MSDN:

using (var context = new BloggingContext()) 
{ 
    using (var dbContextTransaction = context.Database.BeginTransaction()) 
    { 
        try 
        { 
            context.Database.ExecuteSqlCommand( 
                @"UPDATE Blogs SET Rating = 5" + 
                    " WHERE Name LIKE '%Entity Framework%'" 
                ); 

            var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
            foreach (var post in query) 
            { 
                post.Title += "[Cool Blog]"; 
            } 

            context.SaveChanges(); 

            dbContextTransaction.Commit(); 
        } 
        catch (Exception) 
        { 
            dbContextTransaction.Rollback(); //Required according to MSDN article 
            throw; //Not in MSDN article, but recommended so the exception still bubbles up
        } 
    } 
} 

51
try-catch con roolback non è necessario quando si utilizza "using" nella transazione.
Robert,

12
Prendo un'eccezione per intrappolare l'eccezione in questo modo. Fa sì che l'operazione del database fallisca silenziosamente. A causa della natura di SO, qualcuno potrebbe prendere questo esempio e usarlo in un'applicazione di produzione.
B2K,

3
@ B2K: buon punto, ma questo codice viene copiato dall'articolo Microsoft collegato . Spero che nessuno usi il proprio codice per la produzione :)
J Bryan Price

6
@Robert Secondo l'articolo di MSDN Rollback () è necessario. Escludono intenzionalmente un comando di rollback per l'esempio TransactionScope. @ B2K Ho aggiunto lo throw;snippet di MSDN e indicato chiaramente che non è l'originale dell'articolo MSDN.
Todd,

6
(Se corretto) Questo potrebbe chiarire le cose: sembra che EF + MSSQL non abbia bisogno del rollback, ma EF + altri provider SQL potrebbero. Poiché si suppone che EF sia indipendente da quale database stia parlando, Rollback()viene chiamato nel caso in cui stia parlando con MySql o qualcosa che non ha quel comportamento automatico.
Words Like Jared,

-5

Perché alcuni database possono generare un'eccezione su dbContextTransaction.Commit (), quindi meglio:

using (var context = new BloggingContext()) 
{ 
  using (var dbContextTransaction = context.Database.BeginTransaction()) 
  { 
    try 
    { 
      context.Database.ExecuteSqlCommand( 
          @"UPDATE Blogs SET Rating = 5" + 
              " WHERE Name LIKE '%Entity Framework%'" 
          ); 

      var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
      foreach (var post in query) 
      { 
          post.Title += "[Cool Blog]"; 
      } 

      context.SaveChanges(false); 

      dbContextTransaction.Commit(); 

      context.AcceptAllChanges();
    } 
    catch (Exception) 
    { 
      dbContextTransaction.Rollback(); 
    } 
  } 
} 

7
Prendo un'eccezione per intrappolare l'eccezione in questo modo. Fa sì che l'operazione del database fallisca silenziosamente. A causa della natura di SO, qualcuno potrebbe prendere questo esempio e usarlo in un'applicazione di produzione.
B2K,

6
Non è essenzialmente la stessa di questa altra risposta che ha dato l'attribuzione alla pagina MSDN che cita? L'unica differenza che vedo è che si passa falsein context.SaveChanges();, e inoltre chiamare context.AcceptAllChanges();.
Wai Ha Lee,

@ B2K il rollback non è richiesto - se la transazione non funziona, non viene eseguito alcun commit. Anche la chiamata esplicita al rollback può fallire - vedi la mia risposta qui stackoverflow.com/questions/41385740/…
Ken

Il rollback non è ciò a cui mi oppongo. L'autore di questa risposta ha aggiornato il proprio codice per riproporre l'eccezione, risolvendo in tal modo ciò a cui mi opponevo.
B2K,

Mi dispiace, ho commentato dal mio telefono. Todd ribatte l'eccezione, eMeL no. Dovrebbe esserci qualcosa nella cattura che avvisa lo sviluppatore o l'utente di un problema che causa un rollback. Ciò potrebbe essere la scrittura in un file di registro, il ricondizionamento dell'eccezione o la restituzione di un messaggio all'utente.
B2K,
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.