Implementieren einer impliziten Transaktion mit Transaktionsbereich

Mit der TransactionScope-Klasse lassen sich Codeblöcke einfach als an einer Transaktion beteiligte Codeblöcke markieren, ohne die Transaktion selbst bearbeiten zu müssen. Ein Transaktionsbereich kann die Ambient-Transaktion automatisch auswählen und verwalten. Wegen ihrer einfachen Verwendung und Effizienz wird empfohlen, die TransactionScope-Klasse zur Entwicklung von Transaktionsanwendungen zu verwenden.

Außerdem müssen Sie keine Ressourcen für die Transaktion explizit eintragen. Jeder System.Transactions-Ressourcen-Manager (z. B. SQL Server 2005) kann das Vorhandensein einer vom Bereich erstellten Ambient-Transaktion erkennen und diese automatisch eintragen.

Erstellen eines Transaktionsbereichs

Es folgt ein Beispiel für die einfache Verwendung der TransactionScope-Klasse.

'  This function takes arguments for 2 connection strings and commands to create a transaction 
'  involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the 
'  transaction is rolled back. To test this code, you can connect to two different databases 
'  on the same server by altering the connection string, or to another 3rd party RDBMS  
'  by altering the code in the connection2 code block.
Public Function CreateTransactionScope( _
  ByVal connectString1 As String, ByVal connectString2 As String, _
  ByVal commandText1 As String, ByVal commandText2 As String) As Integer

    ' Initialize the return value to zero and create a StringWriter to display results.
    Dim returnValue As Integer = 0
    Dim writer As System.IO.StringWriter = New System.IO.StringWriter

    Try
    ' Create the TransactionScope to execute the commands, guaranteeing
    '  that both commands can commit or roll back as a single unit of work.
        Using scope As New TransactionScope()
            Using connection1 As New SqlConnection(connectString1)
                ' Opening the connection automatically enlists it in the 
                ' TransactionScope as a lightweight transaction.
                connection1.Open()

                ' Create the SqlCommand object and execute the first command.
                Dim command1 As SqlCommand = New SqlCommand(commandText1, connection1)
                returnValue = command1.ExecuteNonQuery()
                writer.WriteLine("Rows to be affected by command1: {0}", returnValue)

                ' If you get here, this means that command1 succeeded. By nesting
                ' the using block for connection2 inside that of connection1, you
                ' conserve server and network resources as connection2 is opened
                ' only when there is a chance that the transaction can commit.   
                Using connection2 As New SqlConnection(connectString2)
                    ' The transaction is escalated to a full distributed
                    ' transaction when connection2 is opened.
                    connection2.Open()

                    ' Execute the second command in the second database.
                    returnValue = 0
                    Dim command2 As SqlCommand = New SqlCommand(commandText2, connection2)
                    returnValue = command2.ExecuteNonQuery()
                    writer.WriteLine("Rows to be affected by command2: {0}", returnValue)
                End Using
            End Using

        ' The Complete method commits the transaction. If an exception has been thrown,
        ' Complete is called and the transaction is rolled back.
        scope.Complete()
        End Using
    Catch ex As TransactionAbortedException
        writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message)
    Catch ex As ApplicationException
        writer.WriteLine("ApplicationException Message: {0}", ex.Message)
    End Try

    ' Display messages.
    Console.WriteLine(writer.ToString())

    Return returnValue
End Function

Der Transaktionsbereich wird begonnen, sobald ein neues TransactionScope-Objekt erstellt wird.  Es wird empfohlen, dass Bereiche, wie im Codebeispiel veranschaulicht, mit einer using-Anweisung erstellt werden. Die using-Anweisung ist sowohl in C# als auch in Visual Basic verfügbar und funktioniert wie ein try...finally-Block, um sicherzustellen, dass der Block ordnungsgemäß freigegeben wird.

Bei der Instanziierung eines TransactionScope bestimmt der Transaktions-Manager, an welcher Transaktion der Bereich beteiligt sein soll. Wenn der Bereich festgelegt ist, ist er immer an dieser Transaktion beteiligt. Die Entscheidung hängt von zwei Faktoren ab: vom Vorhandensein einer umgebenden Transaktion und vom Wert des TransactionScopeOption-Parameters im Konstruktor. Die Ambient-Transaktion ist die Transaktion, in der der Code ausgeführt wird. Ein Verweis auf die Ambient-Transaktion kann durch einen Aufruf der statischen Current-Eigenschaft der Transaction-Klasse abgerufen werden. Weitere Informationen zur Verwendung dieses Parameters finden Sie im Abschnitt "Verwalten des Transaktionsflusses mithilfe von TransactionScopeOption" in diesem Thema.

Vervollständigen eines Transaktionsbereichs

Nachdem die Anwendung alle in einer Transaktion auszuführenden Arbeiten abgeschlossen hat, sollten Sie die Complete-Methode nur einmal aufrufen, um den Transaktions-Manager darüber zu benachrichtigen, dass für die Transaktion ein Commit ausgeführt werden kann. Der Aufruf von Completewird üblicherweise als letzte Anweisung in den using-Block eingefügt.

Wenn diese Methode nicht aufgerufen wird, wird die Transaktion abgebrochen, weil dies vom Transaktions-Manager als Systemfehler oder wie eine im Transaktionsbereich ausgelöste Ausnahme interpretiert wird. Ein Aufruf dieser Methode garantiert aber nicht, dass ein Commit für die Transaktion ausgeführt wird. Dies ist nur eine Möglichkeit, den Transaktions-Manager über den Status zu informieren. Nach dem Aufruf der Complete-Methode können Sie nicht mehr über die Current-Eigenschaft auf die Ambient-Transaktion zugreifen. Wenn Sie dies dennoch versuchen, wird eine Ausnahme ausgelöst.

Wenn das TransactionScope-Objekt die Transaktion ursprünglich erstellt hat, erfolgt die eigentliche Ausführung des Commits durch den Transaktions-Manager in der letzten Codezeile im using-Block. Wenn die Transaktion nicht erstellt wurde, wird der Commit ausgeführt, wenn Commit vom Besitzer des CommittableTransaction-Objekts aufgerufen wird. Nun ruft der Transaktions-Manager die Ressourcen-Manager auf und benachrichtigt sie, entweder ein Commit oder ein Rollback auszuführen, je nachdem, ob die Complete-Methode für das TransactionScope-Objekt aufgerufen wurde.

Mit der using-Anweisung wird sichergestellt, dass die Dispose-Methode des TransactionScope-Objekts auch dann aufgerufen wird, wenn eine Ausnahme auftritt. Der Aufruf der Dispose-Methode kennzeichnet das Ende des Transaktionsbereichs. Ausnahmen, die nach dem Aufrufen dieser Methode eintreten, beeinflussen die Transaktion möglicherweise nicht. Diese Methode stellt auch den vorherigen Zustand der Ambient-Transaktion wieder her.

Eine TransactionAbortedException wird ausgelöst, wenn eine vom Transaktionsbereich erstellte Transaktion abgebrochen wird. Eine TransactionIndoubtException wird ausgelöst, wenn der Transaktions-Manager nicht entscheiden kann, ob ein Commit ausgeführt werden soll. Wenn ein Commit für die Transaktion ausgeführt wird, wird keine Ausnahme ausgelöst..

Rollback einer Transaktion

Wenn ein Rollback für eine Transaktion ausgeführt werden soll, dürfen Sie die Complete-Methode nicht im Transaktionsbereich aufrufen. Zum Beispiel können Sie eine Ausnahme im Bereich auslösen. Der Rollback wird für die Transaktion ausgeführt, die im Bereich liegt.

Verwalten eines Transaktionsflusses mit TransactionScopeOption

Transaktionsbereiche können geschachtelt werden, indem eine Methode aufgerufen wird, die ein TransactionScope-Objekt innerhalb einer Methode verwendet, die ihren eigenen Bereich verwendet. Die RootMethod-Methode im folgenden Beispiel veranschaulicht dies.

void RootMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          SomeMethod();
          scope.Complete();
     }
}

void SomeMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
          /* Perform transactional work here */
          scope.Complete();
     }
}

Der oberste Transaktionsbereich wird als Stammbereich bezeichnet.

Die TransactionScope-Klasse enthält einige überladene Konstruktoren, die eine Enumeration vom Typ TransactionScopeOption akzeptieren, die das Transaktionsverhalten des Bereichs definiert.

Ein TransactionScope-Objekt verfügt über drei Optionen:

  • Verknüpfen der Ambient-Transaktion oder Erstellen einer neuen Transaktion, wenn keine vorhanden ist.

  • Definieren eines neuen Stammbereichs, d. h. Starten einer neuen Transaktion und Festlegen dieser Transaktion als neue Ambient-Transaktion in ihrem eigenem Bereich.

  • Nicht teilnehmen an einer Transaktion. Daraus resultiert keine Ambient-Transaktion.

Wenn der Bereich mit Required instanziiert wird und eine Ambient-Transaktion vorhanden ist, erstellt der Bereich eine Verknüpfung mit dieser Transaktion. Ist keine Ambient-Transaktion vorhanden, erstellt der Bereich eine neue Transaktion und wird zum Stammbereich. Dies ist der Standardwert. Wenn Required verwendet wird, muss der Code innerhalb des Bereichs kein anderes Verhalten zeigen, gleichgültig, ob es sich um den Stammbereich oder nur um eine Verknüpfung mit der Ambient-Transaktion handelt. Er sollte in beiden Fällen das Gleiche ausführen.

Wenn der Bereich mit RequiresNew instanziiert wird, ist er immer der Stammbereich. Er startet eine neue Transaktion, und seine Transaktion wird zur neuen Ambient-Transaktion im Bereich.

Wird der Bereich mit Suppress instanziiert, dann ist er nie an einer Transaktion beteiligt, unabhängig davon, ob eine Ambient-Transaktion vorhanden ist. Für einen mit diesem Wert instanziierten Bereich ist als Ambient-Transaktion immer null definiert.

Die obigen Optionen sind in der folgenden Tabelle zusammengefasst.

TransactionScopeOption Ambient-Transaktion Der Bereich ist beteiligt an

Required

Nein

Neue Transaktion (wird zum Stamm)

Requires New

Nein

Neue Transaktion (wird zum Stamm)

Suppress

Nein

Keine Transaktion

Required

Ja

Ambient-Transaktion

Requires New

Ja

Neue Transaktion (wird zum Stamm)

Suppress

Ja

Keine Transaktion

Wenn ein TransactionScope-Objekt eine Verknüpfung mit einer vorhandenen Ambient-Transaktion erstellt, wird die Transaktion unter Umständen nur dann durch die Freigabe des Bereichsobjekts beendet, wenn der Bereich die Transaktion abbricht. Wenn die Ambient-Transaktion vom Stammbereich erstellt wurde, wird die Commit-Methode nur dann für die Transaktion aufgerufen, wenn der Stammbereich gelöscht wird. Wurde die Transaktion manuell erstellt, dann endet die Transaktion, wenn sie abgebrochen oder von ihrem Ersteller die Commit-Methode aufgerufen wird.

Im folgenden Beispiel werden mit einem TransactionScope-Objekt drei verschachtelte Bereichsobjekte erstellt, die jeweils mit einem anderen TransactionScopeOption-Wert instanziiert werden.

using(TransactionScope scope1 = new TransactionScope()) 
//Default is Required 
{ 
     using(TransactionScope scope2 = new 
      TransactionScope(TransactionScopeOption.Required)) 
     {
     ...
     } 

     using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew)) 
     {
     ...
     } 

     using(TransactionScope scope4 = new 
        TransactionScope(TransactionScopeOption.Suppress)) 
    {
     ...
    } 
}

Im Beispiel wird ein Codeblock ohne Ambient-Transaktion gezeigt, in dem ein neuer Bereich (scope1) mit Required erstellt wird. Der Bereich scope1 ist ein Stammbereich, da er eine neue Transaktion (Transaction A) erstellt, und Transaction A als Ambient-Transaktion definiert. Scope1 erstellt dann drei weitere Objekte, die jeweils über einen anderen TransactionScopeOption-Wert verfügen. Beispielsweise wird scope2 mit Required erstellt, und da eine Ambient-Transaktion vorhanden ist, wird eine Verknüpfung mit der ersten von scope1 erstellten Transaktion erstellt. Beachten Sie, dass scope3 der Stammbereich der neuen Transaktion ist, und dass scope4 keine Ambient-Transaktion enthält.

Der am häufigsten verwendete Wert und Standardwert von TransactionScopeOption lautet zwar Required, aber auch die anderen Werte erfüllen jeweils einen bestimmten Zweck.

Suppress ist nützlich, wenn die im Codeabschnitt ausgeführten Operationen beibehalten werden sollen und die Ambient-Transaktion nicht abgebrochen werden soll, wenn die Operation fehlschlägt. Wenn beispielsweise Aktivitäten protokolliert oder Überwachungsoperationen ausgeführt werden sollen oder wenn Ereignisse für Abonnenten veröffentlicht werden sollen, unabhängig davon, ob die Ambient-Transaktion abgeschlossen oder abgebrochen wird. Dieser Wert ermöglicht es, wie im folgenden Beispiel gezeigt, in einen Transaktionsbereich einen von der Transaktion unabhängigen Codeabschnitt einzufügen.

using(TransactionScope scope1 = new TransactionScope())
{
     try
     {
          //Start of non-transactional section 
          using(TransactionScope scope2 = new
             TransactionScope(TransactionScopeOption.Suppress))
          {
               //Do non-transactional work here
          }
          //Restores ambient transaction here
   }
     catch
     {}
   //Rest of scope1
}

Abstimmen in einem geschachtelten Bereich

Obwohl ein verschachtelter Bereich eine Verknüpfung mit der Ambient-Transaktion des Stammbereichs erstellen kann, wirkt sich ein Aufruf von Complete im verschachtelten Bereich nicht auf den Stammbereich aus. Nur wenn alle Bereiche vom Stammbereich bis zum letzten verschachtelten Bereich dafür stimmen, dass ein Commit für eine Transaktion ausgeführt wird, wird die Transaktion abgeschlossen.

Festlegen des TransactionScope-Timeouts

Einige der überladenen Konstruktoren von TransactionScope akzeptieren einen Wert vom Typ TimeSpan, der zur Steuerung des Timeouts einer Transaktion dient. Ein Timeout mit dem Wert Null (0) steht für ein unendliches Timeout. Ein unendliches Timeout ist vor allem beim Debuggen hilfreich, wenn ein Problem in der Geschäftslogik durch das schrittweise Ausführen des Codes eingegrenzt werden soll, und wenn während der Suche nach der Problemursache bei der Transaktion, die untersucht wird, kein Timeout auftreten soll. Verwenden Sie den unendlichen Timeoutwert in allen anderen Fällen mit äußerster Vorsicht, weil dadurch die Sicherungsmechanismen gegen Transaktions-Deadlocks außer Kraft gesetzt werden.

Sie legen den TransactionScope-Timeout i. d. R. in zwei Fällen auf einen anderen Wert als den Standardwert fest. Erstens während der Entwicklung, wenn Sie testen möchten, wie eine Anwendung mit abgebrochenen Transaktionen umgeht. Wenn Sie für den Timeout einen kleinen Wert (z. B. eine Millisekunde) angeben, kann dies zum Fehlschlagen der Transaktion führen, und Sie können folglich den Fehlerbehandlungscode überprüfen. Zweitens wählen Sie einen kleineren Wert als den Standardtimeoutwert, wenn Sie annehmen, dass der Bereich zu einem Ressourcenkonflikt beiträgt, der zu Deadlocks führt. In diesem Fall soll die Transaktion so bald wie möglich abgebrochen werden und nicht auf den Ablauf des Standardtimeouts warten.

Wenn in einem Bereich eine Verknüpfung mit einer Ambient-Transaktion hergestellt wird, aber ein kleinerer Timeoutwert als der für die Ambient-Transaktion definierte Wert angegeben wird, dann wird der neue kürzere Timeoutwert für das TransactionScope-Objekt übernommen. Wird der Bereich hier nicht innerhalb der in der Ambient-Transaktion angegebenen Zeitspanne beendet, wird die Transaktion automatisch beendet. Wenn der Timeoutwert des geschachtelten Bereichs größer ist als der der Ambient-Transaktion, hat er keine Auswirkungen.

Festlegen der TransactionScope-Isolationsstufe

Einige der überladenen Konstruktoren von TransactionScope akzeptieren neben dem Timeoutwert eine Struktur vom Typ TransactionOptions zur Festlegung der Isolationsstufe. Standardmäßig wird zur Ausführung einer Transaktion die Isolationsstufe auf Serializable festgelegt. Häufig wird für Systeme, die viele Lesevorgänge ausführen müssen, eine andere Isolationsstufe als Serializable gewählt. Dies erfordert ein umfassendes Verständnis der Transaktionsverarbeitungstheorie und der Semantik der betreffenden Transaktion, der einschlägigen Parallelitätsprobleme und der Auswirkungen auf die Systemkonsistenz.

Überdies unterstützen nicht alle Ressourcen-Manager alle Isolationsstufen, sodass ein Ressourcen-Manager die Transaktion möglicherweise auf einer höheren als der konfigurierten Isolationsstufe bearbeitet.

Jede Isolationsstufe außer Serializable ist für Inkonsistenzen anfällig, die sich daraus ergeben können, dass andere Transaktionen auf die gleichen Informationen zugreifen. Die verschiedenen Isolationsstufen unterscheiden sich darin, wie Lese- und Schreibsperren verwendet werden. Eine Sperre kann nur dann gelten, wenn die Transaktion im Ressourcen-Manager auf Daten zugreift. Sie kann aber solange gelten, bis die Transaktion abgeschlossen oder abgebrochen wurde. Im ersten Fall ist der Durchsatz höher, im zweiten Fall die Konsistenz. Da es zwei Arten von Sperren und zwei Arten von Operationen (Lesen/Schreiben) gibt, sind vier grundlegende Isolationsstufen verfügbar. Weitere Informationen finden Sie unter IsolationLevel.

Beim Einsatz verschachtelter TransactionScope-Objekte muss für alle verschachtelten Bereiche die gleiche Isolationsstufe konfiguriert werden, wenn eine Verknüpfung mit der Ambient-Transaktion hergestellt werden soll Wenn verschachtelte TransactionScope-Objekte eine Verknüpfung mit der Ambient-Transaktion herzustellen versuchen und für diese eine andere Isolationsstufe festgelegt wurde, dann wird eine Ausnahme des Typs ArgumentException ausgelöst.

Zusammenarbeit mit COM+

Wenn Sie eine neue TransactionScope-Instanz erstellen, können Sie die EnterpriseServicesInteropOption-Enumeration in einem der Konstruktoren verwenden, um die Zusammenarbeit mit COM+ näher zu bestimmen. Weitere Informationen hierzu finden Sie unter Interoperabilität mit Enterprise Services und COM+-Transaktionen.

Siehe auch

Referenz

Clone
TransactionScope

Footer image

Copyright © 2007 by Microsoft Corporation. Alle Rechte vorbehalten.