9 Stimmen

Transaktion innerhalb einer Transaktion in C#

Ich importiere eine flache Datei mit Rechnungen in eine Datenbank mit C#. Ich verwende den TransactionScope, um den gesamten Vorgang zurückzusetzen, wenn ein Problem auftritt.

Es handelt sich um eine schwierige Eingabedatei, da eine Zeile nicht unbedingt einem Datensatz entspricht. Sie enthält auch verknüpfte Datensätze. Eine Rechnung hat eine Kopfzeile, Einzelposten und eine Gesamtzeile. Einige der Rechnungen müssen übersprungen werden, aber es kann sein, dass ich nicht weiß, dass sie übersprungen werden müssen, bis ich die Gesamtzeile erreiche.

Eine Strategie besteht darin, die Kopfzeile, die Einzelposten und die Gesamtzeile im Speicher abzulegen und alles zu speichern, sobald die Gesamtzeile erreicht ist. Das verfolge ich jetzt.

Ich habe mich jedoch gefragt, ob es nicht auch anders gehen könnte. Ich würde eine "verschachtelte" Transaktion um die Rechnung herum erstellen, die Kopfzeile und die Einzelposten einfügen und dann die Rechnung aktualisieren, wenn die Gesamtzeile erreicht ist. Diese "verschachtelte" Transaktion würde zurückgehen, wenn festgestellt wird, dass die Rechnung übersprungen werden muss, aber die Gesamttransaktion würde fortgesetzt.

Ist dies möglich, praktisch, und wie würden Sie es einrichten?

43voto

Aaronaught Punkte 118136

Weder die TransactionScope noch unterstützt SQL Server verschachtelte Transaktionen.

Sie können verschachteln TransactionScope Instanzen, aber das hat nur den äußeren Anschein einer verschachtelten Transaktion. In Wirklichkeit handelt es sich um eine so genannte "umgebende" Transaktion, und es kann immer nur eine geben. Welche Transaktion die umgebende Transaktion ist, hängt davon ab, was Sie für TransactionScopeOption wenn Sie den Bereich erstellen.

Zur näheren Erläuterung sei Folgendes angeführt:

using (var outer = new TransactionScope())
{
    DoOuterWork();

    using (var inner1 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        DoWork1();
        inner1.Complete();
    }

    using (var inner2 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        DoWork2();
        inner2.Complete();
    }

    using (var inner3 = new TransactionScope(TransactionScopeOption.Required))
    {
        DoWork3();
        inner3.Complete();
    }

    outer.Complete();
}

Im Folgenden wird beschrieben, was bei den einzelnen inneren Bereichen geschieht:

  • inner1 wird in einer impliziten Transaktion ausgeführt, unabhängig von outer . Nichts, was in der DoWork1 ist garantiert atomar. Wenn dies auf halbem Weg scheitert, haben Sie inkonsistente Daten. Jede Arbeit, die hier stattfindet, wird immer übertragen, unabhängig davon, was mit outer .

  • inner2 wird in einer neuen Transaktion ausgeführt, unabhängig von outer . Dies ist eine verschiedene Transaktion von outer aber es ist no verschachtelt. Schlägt es fehl, wird die Arbeit, die in outer ( DoOuterWork() ) und jeder der anderen Bereiche kann immer noch begangen werden, aber hier ist der Haken: Wenn dies der Fall ist, wird die gesamte outer Transaktion wird no die im Inneren geleistete Arbeit rückgängig machen inner2 . Aus diesem Grund ist sie nicht wirklich verschachtelt. Auch, inner2 hat keinen Zugriff auf Zeilen, die von outer Wenn Sie also nicht aufpassen, kann es hier zu Deadlocks kommen.

  • inner3 wird in der dieselbe Transaktion als outer . Dies ist das Standardverhalten. Wenn DoWork3() scheitert und inner3 nie abgeschlossen wird, dann wird die gesamte outer Transaktion wird zurückgenommen. Ähnlich verhält es sich, wenn inner3 wird erfolgreich abgeschlossen, aber outer rückgängig gemacht wird, dann wird jede Arbeit, die in DoWork3() wird ebenfalls rückgängig gemacht.

Sie können also hoffentlich sehen, dass keine dieser Optionen tatsächlich verschachtelt ist und Ihnen nicht das gibt, was Sie wollen. Die Required ähnelt einer verschachtelten Transaktion, gibt Ihnen aber nicht die Möglichkeit, bestimmte Arbeitseinheiten innerhalb der Transaktion unabhängig voneinander zu übertragen oder zurückzusetzen.

Das, was einer echten verschachtelten Transaktion in SQL Server am nächsten kommt, ist die SAVE TRAN Anweisung kombiniert mit einigen TRY/CATCH Blöcke. Wenn Sie Ihre Logik in einer oder mehreren Stored Procedures unterbringen können, wäre dies eine gute Option.

Andernfalls müssen Sie, wie von Oded vorgeschlagen, für jede Rechnung separate Transaktionen verwenden.

3voto

Remus Rusanu Punkte 280155

Dies wird erreicht durch eine Transaktionsspeicherpunkt . Normalerweise sieht es etwa so aus:

BEGIN TRANSACTION
for each invoice
   SAVE TRANSACTION InvoiceStarted
   BEGIN TRY
     Save header
     Save line 1
     Save line 2
     Save Total
   END TRY
   BEGIN CATCH
     ROLLBACK TO Invoicestarted 
     Log Failed Invoice
   END CATCH
end for
COMMIT

Ich habe einen Transact-SQL-basierten Pseudocode verwendet, und das ist kein Zufall. Savepoints sind ein Datenbankkonzept und die .Net-Transaktionen unterstützen sie nicht. Sie können SqlTransaction direkt verwenden und die SqlTransaction.Save oder Sie können T-SQL gespeicherte Prozeduren verwenden, die nach dem Vorbild einer Ausnahmesichere Vorlage . Ich würde empfehlen, Sie vermeiden die .Net-Transaktionen (dh TransactionScope) in diesem Fall.

2voto

Oded Punkte 475566

Anstatt verschachtelte Transaktionen zu verwenden, könnten Sie eine Transaktion pro Rechnung erstellen. Auf diese Weise werden nur erfolgreiche Aktualisierungen für ganze Rechnungen vorgenommen.

Wenn Sie Transaktionen auf die von Ihnen beschriebene Weise verschachteln, besteht die Gefahr, dass der gesamte Datensatz zurückgerollt wird, was nicht erwünscht ist.

2voto

David_001 Punkte 5433

Ich persönlich würde zunächst prüfen, ob die Rechnung hinzugefügt werden muss - wenn ja, dann fügen Sie sie (in einer Transaktion) ein. Andernfalls würden Sie einfach zur nächsten Rechnung übergehen.

Ich glaube nicht, dass es so toll ist, etwas einzufügen und dann ein Rollback zu machen, so wie Sie es beschreiben.

0voto

Mark Brackett Punkte 83046

Eine fehlgeschlagene innere Transaktion würde die äußere Transaktion zurücksetzen, so dass Sie diesen Weg nicht gehen können.

Sie können es aber wahrscheinlich vortäuschen, indem Sie eine temporäre Tabelle (oder eine Lasttabelle) verwenden. Fügen Sie jede Rechnung transaktional in die Lasttabelle ein und verschieben Sie sie dann atomar von der Lasttabelle in eine permanente Tabelle.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X