Transaktions-Rollback für Entity Framework 6

80

Mit EF6 haben Sie eine neue Transaktion, die wie folgt verwendet werden kann:

using (var context = new PostEntityContainer())
        {
            using (var dbcxtransaction = context.Database.BeginTransaction())
            {
                try
                {
                    PostInformation NewPost = new PostInformation()
                    {
                        PostId = 101,
                        Content = "This is my first Post related to Entity Model",
                        Title = "Transaction in EF 6 beta"
                    };
                    context.Post_Details.Add(NewPost);
                    context.SaveChanges();
                    PostAdditionalInformation PostInformation = new PostAdditionalInformation()
                    {
                        PostId = (101),
                        PostName = "Working With Transaction in Entity Model 6 Beta Version"
                    };

                    context.PostAddtional_Details.Add(PostInformation);
                    context.SaveChanges();

                    dbcxtransaction.Commit();
                }
                catch
                {
                    dbcxtransaction.Rollback();
                }
            }
        }

Wird ein Rollback tatsächlich benötigt, wenn die Dinge seitwärts gehen? Ich bin neugierig, weil in der Commit-Beschreibung steht: "Commit die zugrunde liegende Store-Transaktion."

In der Rollback-Beschreibung heißt es: "Rollt die zugrunde liegende Geschäftstransaktion zurück."

Das macht mich neugierig, denn es sieht so aus, als würden die zuvor ausgeführten Befehle nicht gespeichert, wenn Commit nicht aufgerufen wird (was mir logisch erscheint). Aber wenn dies der Fall ist, was wäre der Grund, die Rollback-Funktion aufzurufen? In EF5 habe ich TransactionScope verwendet, das keine Rollback-Funktion (nur eine vollständige) hatte, was mir logisch erschien. Aus MS-DTC-Gründen kann ich das TransactionScope nicht mehr verwenden, aber ich kann auch keinen Try-Catch wie im obigen Beispiel verwenden (dh ich benötige nur das Commit).

Der Kekshund
quelle
1
Haben Sie sich über Transaktionen in SQL informiert ? EF versucht das nachzuahmen. AFAIK: Wenn Sie keine Transaktion in SQL festschreiben, wird sie zurückgesetzt.
gunr2171
Siehe auch diese Frage .
gunr2171
Ja, ich kenne Transaktionen in SQL selbst. Ich war neugierig, was EF macht, aber wenn sie das nachahmen, macht es Sinn. Ich werde sehen, ob ich es umgehen kann. Vielen Dank!
Der Kekshund
SaveChanges () tritt immer in einer Transaktion auf, die im Falle einer Ausnahme zurückgesetzt wird. In Ihrem Fall müssen Sie nicht versuchen, dies manuell zu handhaben (in diesem speziellen Fall ist es sogar noch besser, alle Entitäten zuerst und SaveChangesnur einmal hinzuzufügen ).
Pawel
Ich möchte nur, dass Elemente aus beiden SaveChanges gespeichert werden, wenn beide nicht fehlschlagen. Ja, ich benötige eine einzelne Transaktion für beide.
The Cookies Dog

Antworten:

116

Sie müssen nicht Rollbackmanuell aufrufen, da Sie die usingAnweisung verwenden.

DbContextTransaction.DisposeMethode wird am Ende des usingBlocks aufgerufen . Die Transaktion wird automatisch zurückgesetzt, wenn die Transaktion nicht erfolgreich festgeschrieben wurde (keine aufgerufenen oder aufgetretenen Ausnahmen). Es folgt der Quellcode der SqlInternalTransaction.DisposeMethode ( DbContextTransaction.Disposewird bei Verwendung des SqlServer-Anbieters endgültig an ihn delegiert):

private void Dispose(bool disposing)
{
    // ...
    if (disposing && this._innerConnection != null)
    {
        this._disposing = true;
        this.Rollback();
    }
}

Sie sehen, es prüft, ob _innerConnectionnicht null ist. Wenn nicht, wird die Transaktion zurückgesetzt (wenn festgeschrieben, _innerConnectionist sie null). Mal sehen, was Commitmacht:

internal void Commit() 
{
    // Ignore many details here...

    this._innerConnection.ExecuteTransaction(...);

    if (!this.IsZombied && !this._innerConnection.IsYukonOrNewer)
    {
        // Zombie() method will set _innerConnection to null
        this.Zombie();
    }
    else
    {
        this.ZombieParent();
    }

    // Ignore many details here...
}

internal void Zombie()
{
    this.ZombieParent();

    SqlInternalConnection innerConnection = this._innerConnection;

    // Set the _innerConnection to null
    this._innerConnection = null;

    if (innerConnection != null)
    {
        innerConnection.DisconnectTransaction(this);
    }
}
Mouhong Lin
quelle
23

Solange Sie immer SQL Server mit EF verwenden, müssen Sie den catch nicht explizit verwenden, um die Rollback-Methode aufzurufen. Das automatische Zurücksetzen des using-Blocks bei Ausnahmen funktioniert immer.

Wenn Sie jedoch aus der Sicht von Entity Framework darüber nachdenken, können Sie sehen, warum alle Beispiele den expliziten Aufruf zum Rollback der Transaktion verwenden. Für den EF ist der Datenbankanbieter beliebig und steckbar, und der Anbieter kann durch MySQL oder eine andere Datenbank mit einer EF-Anbieterimplementierung ersetzt werden. Aus EF-Sicht gibt es daher keine Garantie dafür, dass der Anbieter die veräußerte Transaktion automatisch zurücksetzt, da der EF nichts über die Implementierung des Datenbankanbieters weiß.

In der EF-Dokumentation wird daher als bewährte Methode empfohlen, ein explizites Rollback durchzuführen - für den Fall, dass Sie eines Tages den Anbieter auf eine Implementierung umstellen, die bei der Entsorgung kein automatisches Rollback durchführt.

Meiner Meinung nach wird jeder gute und gut geschriebene Anbieter die Transaktion in der Dispose automatisch zurücksetzen, so dass der zusätzliche Aufwand, alles innerhalb des using-Blocks mit einem Try-Catch-Rollback zu verpacken, übertrieben ist.

Rwb
quelle
1
Vielen Dank für diesen Einblick. Ich habe mich in den Code vertieft und Sie landen bei der abstrakten Klasse DbTransaction's Dispose, die in SqlTransaction überschrieben wird, die selbst die von Mouhong Lin erwähnte SqlInternalTransaction aufruft.
ShawnFumo
4
  1. Da Sie einen 'using'-Block geschrieben haben, um die Transaktion zu instanziieren, müssen Sie die Rollback-Funktion nicht explizit erwähnen, da sie zum Zeitpunkt der Entsorgung automatisch zurückgesetzt wird (sofern sie nicht festgeschrieben wurde).
  2. Wenn Sie es jedoch ohne einen using-Block instanziieren, ist es in diesem Fall wichtig, die Transaktion im Falle einer Ausnahme (genau in einem catch-Block) zurückzusetzen, und dies auch mit einer Nullprüfung für einen robusteren Code. Die Funktionsweise von BeginTransaction unterscheidet sich vom Transaktionsbereich (der nur eine vollständige Funktion benötigt, wenn alle Vorgänge erfolgreich abgeschlossen wurden). Stattdessen ähnelt es der Funktionsweise von SQL-Transaktionen.
roopaliv
quelle