Verwenden von Transaktionen oder SaveChanges (false) und AcceptAllChanges ()?

346

Ich habe untersucht worden Transaktionen und es scheint , dass sie sich selbst in EF kümmern, solange ich pass falseauf SaveChanges()und rufen Sie dann , AcceptAllChanges()wenn es keine Fehler sind:

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

Was ist, wenn etwas schief geht? Muss ich kein Rollback durchführen oder ist die Transaktion beendet, sobald meine Methode den Gültigkeitsbereich verlässt?

Was passiert mit Einrückungsspalten, die nach der Hälfte der Transaktion zugewiesen wurden? Ich nehme an, wenn jemand anderes einen Datensatz nach meinem hinzugefügt hat, bevor meiner schlecht wurde, bedeutet dies, dass ein Identitätswert fehlt.

Gibt es einen Grund, die Standardklasse TransactionScopein meinem Code zu verwenden?

Mark Smith
quelle
1
Dies half mir zu verstehen, warum SaveChanges(fase); ... AcceptAllChanges();es überhaupt ein Muster gab. Beachten Sie, wie die akzeptierte Antwort auf die obige Frage vom Autor eines Blogs geschrieben wird - und auf dieses Blog wird in der anderen Frage verwiesen. Es kommt alles zusammen.
Die rote Erbse

Antworten:

451

Mit dem Entity Framework ist die meiste Zeit SaveChanges()ausreichend. Dadurch wird eine Transaktion erstellt oder eine Umgebungstransaktion eingetragen, und alle erforderlichen Arbeiten für diese Transaktion werden ausgeführt.

Manchmal ist die SaveChanges(false) + AcceptAllChanges()Paarung jedoch nützlich.

Der nützlichste Ort hierfür ist in Situationen, in denen Sie eine verteilte Transaktion über zwei verschiedene Kontexte ausführen möchten.

Dh so etwas (schlecht):

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

Wenn dies context1.SaveChanges()erfolgreich ist, aber context2.SaveChanges()fehlschlägt, wird die gesamte verteilte Transaktion abgebrochen. Leider hat das Entity Framework die Änderungen bereits verworfen context1, sodass Sie den Fehler nicht wiederholen oder effektiv protokollieren können.

Aber wenn Sie Ihren Code so ändern, dass er so aussieht:

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

}

Während der Aufruf zum SaveChanges(false)Senden der erforderlichen Befehle an die Datenbank wird der Kontext selbst nicht geändert, sodass Sie ihn bei Bedarf erneut ausführen oder bei Bedarf abfragen können ObjectStateManager.

Dies bedeutet, wenn die Transaktion tatsächlich eine Ausnahme auslöst, können Sie diese kompensieren, indem Sie den Status jedes Kontexts entweder erneut versuchen oder protokollieren ObjectStateManager.

Weitere Informationen finden Sie in meinem Blogbeitrag .

Alex James
quelle
3
Das ist großartig, danke ... Wenn also etwas ausfällt, muss ich nicht zurückrollen? SaveChanges markiert es als gespeichert, schreibt es aber erst fest, wenn ich alle Änderungen akzeptiere. Aber wenn etwas schief geht, muss ich ein Rollback durchführen, nicht wahr, damit mein Objekt in den richtigen Zustand zurückkehrt?
Mark Smith
33
@Mark: Wenn Sie mit "Rollback" meinen, dass Sie Ihre Objekte auf den Status zurücksetzen, in dem sie sich in der Datenbank befinden, dann möchten Sie dies nicht tun, da Sie alle Änderungen des Benutzers an den Objekten verlieren würden . SaveChanges(false)führt die eigentliche Aktualisierung der Datenbank durch und AcceptAllChanges()teilt EF mit: "Okay, Sie können vergessen, welche Dinge gespeichert werden müssen, da sie erfolgreich gespeichert wurden." Wenn dies SaveChanges(false)fehlschlägt, AcceptAllChanges()wird es niemals aufgerufen, und EF betrachtet Ihr Objekt weiterhin als Eigenschaften, die geändert wurden und in der Datenbank gespeichert werden müssen.
BlueRaja - Danny Pflughoeft
Können Sie uns raten, wie Sie dies mit Code First tun sollen? Es gibt keinen Parameter für SaveChanges oder AcceptAllChanges Methode
Kirsten Greed
2
Ich habe eine Frage zur Verwendung dieser Technik mit Code First hier gestellt
Kirsten Greed
13
Dies ist in EF 6.1 nicht mehr möglich. Wissen Sie, welche Anpassungen vorgenommen werden müssen, um jetzt zu funktionieren?
Alex Dresko
113

Wenn Sie EF6 (Entity Framework 6+) verwenden, hat sich dies für Datenbankaufrufe an SQL geändert.
Siehe: http://msdn.microsoft.com/en-us/data/dn456843.aspx

Verwenden Sie context.Database.BeginTransaction.

Von 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
        } 
    } 
} 
user3885816
quelle
51
try-catch mit Roolback ist nicht erforderlich, wenn Sie "using" für die Transaktion verwenden.
Robert
12
Ich mache eine Ausnahme, um die Ausnahme so abzufangen. Dadurch schlägt der Datenbankvorgang unbemerkt fehl. Aufgrund der Natur von SO kann jemand dieses Beispiel nehmen und es in einer Produktionsanwendung verwenden.
B2K
3
@ B2K: Guter Punkt, aber dieser Code wird aus dem verlinkten Microsoft-Artikel kopiert . Ich hoffe, niemand verwendet ihren Code in der Produktion :)
J Bryan Price
6
@Robert Laut MSDN-Artikel ist Rollback () notwendig. Sie lassen absichtlich einen Rollback-Befehl für das TransactionScope-Beispiel weg. @ B2K Ich habe im throw;zum MSDN-Snippet hinzugefügt und klar angegeben, dass es nicht das Original aus dem MSDN-Artikel ist.
Todd
6
(Wenn richtig) Dies könnte Abhilfe schaffen: Klingt so, als ob EF + MSSQL kein Rollback benötigt, EF + andere SQL-Anbieter jedoch möglicherweise. Da EF Rollback()unabhängig davon sein soll, mit welcher Datenbank es spricht, wird es aufgerufen, falls es mit MySQL spricht oder etwas, das dieses automatische Verhalten nicht aufweist.
Wörter wie Jared
-5

Da einige Datenbanken bei dbContextTransaction.Commit () eine Ausnahme auslösen können, ist dies besser:

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(); 
    } 
  } 
} 
eMeL
quelle
7
Ich mache eine Ausnahme, um die Ausnahme so abzufangen. Dadurch schlägt der Datenbankvorgang unbemerkt fehl. Aufgrund der Natur von SO kann jemand dieses Beispiel nehmen und es in einer Produktionsanwendung verwenden.
B2K
6
Ist dies nicht im Wesentlichen dasselbe wie diese andere Antwort, die der zitierten MSDN-Seite eine Zuschreibung gab ? Der einzige Unterschied , den ich sehe , ist , dass Sie passieren falsein context.SaveChanges();und zusätzlich nennen context.AcceptAllChanges();.
Wai Ha Lee
@ B2K ist das Rollback nicht erforderlich - wenn die Transaktion nicht funktioniert, wird nichts festgeschrieben. Auch ein expliziter Aufruf von Rollback kann fehlschlagen - siehe meine Antwort hier stackoverflow.com/questions/41385740/…
Ken
Das Rollback ist nicht das, was ich ablehne. Der Autor dieser Antwort hat seinen Code aktualisiert, um die Ausnahme erneut auszulösen, und damit das gelöst, was ich beanstandet habe.
B2K
Entschuldigung, ich habe von meinem Handy aus kommentiert. Todd löst die Ausnahme erneut aus, eMeL nicht. Der Haken sollte etwas enthalten, das entweder den Entwickler oder den Benutzer über ein Problem informiert, das einen Rollback verursacht. Dies kann das Schreiben in eine Protokolldatei, das erneute Auslösen der Ausnahme oder das Zurücksenden einer Nachricht an den Benutzer sein.
B2K