Implementieren einer expliziten Transaktion mit CommittableTransaction

Die CommittableTransaction-Klasse ermöglicht es Anwendungen, Transaktionen explizit zu verwenden, anstatt die TransactionScope-Klasse implizit zu verwenden. Sie ist für Anwendungen nützlich, die dieselben Transaktionen über mehrere Funktionsaufrufe oder mehrere Threadaufrufe hinweg verwenden wollen. Im Unterschied zur TransactionScope-Klasse muss der Autor der Anwendung die Commit-Methode bzw. die Rollback-Methode aufrufen, um einen Commit der Transaktion auszuführen oder um sie abzubrechen.

Übersicht über die CommittableTransaction-Klasse

Die CommittableTransaction-Klasse ist von der Transaction-Klasse abgeleitet und stellt deshalb die gesamte Funktionalität der letztgenannten bereit. Besonders nützlich ist die Rollback-Methode für die Transaction-Klasse, die auch dazu verwendet werden kann, ein Rollback für ein CommittableTransaction-Objekt auszuführen.

Die Transaction-Klasse ist der CommittableTransaction-Klasse ähnlich, bietet aber keine Commit-Methode. Dadurch haben Sie die Möglichkeit, das Transaktionsobjekt (oder Klone davon) an andere Methoden zu übergeben (eventuell solche anderer Threads), während Sie weiter steuern können, wann ein Commit für die Transaktion ausgeführt wird. Der abgerufene Code kann sich für eine Transaktion eintragen und über sie abstimmen, jedoch kann nur der Ersteller des CommittableTransaction-Objekts ein Commit für die Transaktion ausführen.

Berücksichtigen Sie die folgenden Aspekte, wenn Sie die CommittableTransaction-Klasse verwenden.

  • Durch das Erstellen einer CommittableTransaction-Transaktion wird die Ambient-Transaktion nicht festgelegt. Sie müssen die Ambient-Transaktion explizit festlegen und zurücksetzen, um sicherzustellen, dass Ressourcen-Manager im richtigen Transaktionskontext arbeiten. Die aktuelle Ambient-Transaktion wird durch Einstellen der statischen Current-Eigenschaft für das globale Transaction-Objekt festgelegt.

  • Ein CommittableTransaction-Objekt kann nicht wiederverwendet werden. Nachdem ein Commit oder ein Rollback für ein CommittableTransaction-Objekt ausgeführt wurde, kann es nicht in einer Transaktion wiederverwendet werden. Das heißt, es kann nicht als aktueller Ambient-Transaktionskontext festgelegt werden.

Erstellen einer CommittableTransaction

Im folgenden Beispiel wird eine neue Instanz von CommittableTransaction erstellt und ein Commit dafür ausgeführt.

//Create a committable transaction
tx = new CommittableTransaction();

SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=northwind");
SqlCommand myCommand = new SqlCommand();

//Open the SQL connection
myConnection.Open();

//Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx);

myCommand.Connection = myConnection;

// Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
myCommand.ExecuteNonQuery();

// Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
myCommand.ExecuteNonQuery();

// Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
myCommand.ExecuteNonQuery();

// Commit or rollback the transaction
while (true)
{
    Console.Write("Commit or Rollback? [C|R] ");
    ConsoleKeyInfo c = Console.ReadKey();
    Console.WriteLine();

    if ((c.KeyChar == 'C') || (c.KeyChar == 'c'))
    {
        tx.Commit();
        break;
    }
    else if ((c.KeyChar == 'R') || (c.KeyChar == 'r'))
    {
        tx.Rollback();
        break;
    }
}
myConnection.Close();
tx = null;
tx = New CommittableTransaction

Dim myConnection As New SqlConnection("server=(local)\SQLExpress;Integrated Security=SSPI;database=northwind")
Dim myCommand As New SqlCommand()

'Open the SQL connection
myConnection.Open()

'Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx)

myCommand.Connection = myConnection

'Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"
myCommand.ExecuteNonQuery()

'Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"
myCommand.ExecuteNonQuery()

'Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"
myCommand.ExecuteNonQuery()

'Commit or rollback the transaction
Dim c As ConsoleKeyInfo
While (True)
    Console.Write("Commit or Rollback? [C|R] ")
    c = Console.ReadKey()
    Console.WriteLine()

    If (c.KeyChar = "C") Or (c.KeyChar = "c") Then
        tx.Commit()
        Exit While
    ElseIf ((c.KeyChar = "R") Or (c.KeyChar = "r")) Then
        tx.Rollback()
        Exit While
    End If
End While

myConnection.Close()
tx = Nothing

Durch das Erstellen einer Instanz von CommittableTransaction wird der Ambient-Transaktionskontext nicht automatisch festgelegt. Deshalb sind Vorgänge, die für einen Ressourcen-Manager ausgeführt werden, nicht Teil dieser Transaktion. Die statische Current-Eigenschaft für das globale Transaction-Objekt wird dazu verwendet, die Ambient-Transaktion festzulegen oder abzurufen. Die Anwendung muss sie manuell einstellen, um sicherzustellen, dass Ressourcen-Manager an der Transaktion teilnehmen können. Es wird zudem empfohlen, die alte Ambient-Transaktion zu speichern und nach Beendigung aller Vorgänge mit dem CommittableTransaction-Objekt wiederherzustellen.

Um ein Commit der Transaktion auszuführen, müssen Sie die Commit-Methode explizit aufrufen. Um ein Rollback einer Transaktion auszuführen, müssen Sie die Rollback-Methode aufrufen. Beachten Sie, dass alle Ressourcen, die von einer Transaktion betroffen sind, gesperrt bleiben, bis ein Commit oder ein Rollback für eine CommittableTransaction ausgeführt wurde.

Ein CommittableTransaction-Objekt kann über Funktionsaufrufe und Threads hinweg verwendet werden. Jedoch muss der Anwendungsentwickler Ausnahmen bearbeiten und die Rollback(Exception)-Methode beim Auftreten von Fehlern explizit aufrufen.

Asynchroner Commit

Die CommittableTransaction-Klasse stellt zudem einen Mechanismus bereit, um ein Commit für eine Transaktion asynchron auszuführen. Ein Transaktionscommit kann viel Zeit in Anspruch nehmen, da eventuell mehrfach auf Datenbanken zugegriffen werden muss. Außerdem bestehen im Netzwerk möglicherweise Wartezeiten aufgrund der Netzwerklatenz. Wenn Sie Deadlocks in Anwendungen mit hohem Durchsatz vermeiden möchten, können Sie einen asynchronen Commit durchführen, um die Transaktionsaufgaben so schnell wie möglich abzuschließen, und den Commitvorgang als Hintergrundaufgabe ausführen. Die BeginCommit-Methode und die EndCommit-Methode der CommittableTransaction-Klasse ermöglichen dies.

Sie können BeginCommit aufrufen, um den zurückgehaltenen Commit an einen Thread aus dem Threadpool weiterzuleiten. Sie können zudem EndCommit aufrufen, um zu ermitteln, ob ein Commit für die Transaktion ausgeführt wurde. Wenn der Commit aus irgendeinem Grund nicht für die Transaktion ausgeführt wurde, löst EndCommit eine Transaktionsausnahme aus. Wurde der Transaktionscommit zum Zeitpunkt des Aufrufs von EndCommit noch nicht ausgeführt, wird der Aufrufer gesperrt, bis der Commit durchgeführt oder die Transaktion abgebrochen wurde.

Das einfachste Verfahren, einen asynchronen Commit auszuführen, besteht darin, eine Rückrufmethode einzurichten. Dann erfolgt bei Beendigung des Commit ein Rückruf. Jedoch müssen Sie die EndCommit-Methode für das ursprüngliche CommittableTransaction-Objekt aufrufen, das für den Aufruf verwendet wurde. Um dieses Objekt zu erhalten, können Sie den IAsyncResult-Parameter der Rückrufmethode umwandeln, da die CommittableTransaction-Klasse die IAsyncResult-Klasse implementiert.

Das folgende Beispiel zeigt, wie ein asynchroner Commit ausgeführt werden kann.

public void DoTransactionalWork()  
{  
     Transaction oldAmbient = Transaction.Current;  
     CommittableTransaction committableTransaction = new CommittableTransaction();  
     Transaction.Current = committableTransaction;  
  
     try  
     {  
          /* Perform transactional work here */  
          // No errors - commit transaction asynchronously  
          committableTransaction.BeginCommit(OnCommitted,null);  
     }  
     finally  
     {  
          //Restore the ambient transaction
          Transaction.Current = oldAmbient;  
     }  
}  
void OnCommitted(IAsyncResult asyncResult)  
{  
     CommittableTransaction committableTransaction;  
     committableTransaction = asyncResult as CommittableTransaction;
     Debug.Assert(committableTransaction != null);  
     try  
     {  
          using(committableTransaction)  
          {  
               committableTransaction.EndCommit(asyncResult);  
          }  
     }  
     catch(TransactionException e)  
     {  
          //Handle the failure to commit  
     }  
}  

Siehe auch