Freigeben über


Implementieren einer expliziten Transaktion mit CommittableTransaction

Die CommittableTransaction Klasse bietet eine explizite Möglichkeit für Anwendungen, eine Transaktion zu verwenden, im Gegensatz zur impliziten Verwendung der TransactionScope Klasse. Es ist nützlich für Anwendungen, die dieselbe Transaktion für mehrere Funktionsaufrufe oder mehrere Threadaufrufe verwenden möchten. Im Gegensatz zu der TransactionScope-Klasse muss der Anwendungsentwickler die Commit- und Rollback-Methoden speziell aufrufen, um die Transaktion abzuschließen oder abzubrechen.

Übersicht über die CommittableTransaction-Klasse

Die CommittableTransaction Klasse wird von der Transaction Klasse abgeleitet, sodass alle Funktionen dieser Klasse bereitgestellt werden. Besonders hilfreich ist die Rollback Methode für die Transaction Klasse, die auch zum Rollback eines CommittableTransaction Objekts verwendet werden kann.

Die Transaction Klasse ähnelt der CommittableTransaction Klasse, bietet Commit aber keine Methode. Auf diese Weise können Sie das Transaktionsobjekt (oder Klone davon) an andere Methoden (potenziell in anderen Threads) übergeben und gleichzeitig steuern, wann die Transaktion abgeschlossen wird. Der aufgerufene Code kann die Transaktion auflisten und abstimmen, aber nur der Ersteller des CommittableTransaction Objekts hat die Möglichkeit, die Transaktion zu übernehmen.

Beachten Sie beim Arbeiten mit der CommittableTransaction Klasse Folgendes:

  • Durch das Erstellen einer CommittableTransaction-Transaktion wird die Ambient-Transaktion nicht festgelegt. Sie müssen die Umgebungstransaktion gezielt festlegen und zurücksetzen, um sicherzustellen, dass Ressourcenmanager bei Bedarf im richtigen Transaktionskontext arbeiten. Die Möglichkeit zum Festlegen der aktuellen Umgebungstransaktion besteht darin, die statische Current Eigenschaft für das globale Transaction Objekt festzulegen.

  • Ein CommittableTransaction Objekt kann nicht wiederverwendet werden. Nachdem ein CommittableTransaction Objekt zugesichert oder zurückgesetzt wurde, kann es nicht mehr in einer Transaktion verwendet werden. Das heißt, sie kann nicht als aktueller Umgebungstransaktionskontext 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

Beim Erstellen einer Instanz von CommittableTransaction wird der Umgebungstransaktionskontext 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 verwendet, um die Umgebungstransaktion festzulegen oder abzurufen, und die Anwendung muss sie manuell festlegen, um sicherzustellen, dass Ressourcenmanager an der Transaktion teilnehmen können. Außerdem empfiehlt es sich, die alte Umgebungstransaktion zu speichern und wiederherzustellen, wenn Sie mit der Verwendung des CommittableTransaction Objekts fertig sind.

Um die Transaktion zu übernehmen, müssen Sie die Commit Methode explizit aufrufen. Für das Rollback einer Transaktion sollten 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. Es liegt jedoch an dem Anwendungsentwickler, Ausnahmen zu behandeln und die Rollback(Exception) Methode im Falle von Fehlern speziell aufzurufen.

Asynchroner Commit

Die CommittableTransaction Klasse stellt außerdem einen Mechanismus zum asynchronen Commit einer Transaktion bereit. Ein Transaktions-Commit kann erhebliche Zeit in Anspruch nehmen, da er mehrere Datenbankzugriffe und mögliche Netzwerklatenz umfassen kann. Wenn Sie Deadlocks in Anwendungen mit hohem Durchsatz vermeiden möchten, können Sie asynchrones Commit verwenden, um die Transaktionsarbeit so schnell wie möglich abzuschließen und den Commitvorgang als Hintergrundaufgabe auszuführen. Die Methoden BeginCommit und EndCommit der CommittableTransaction Klasse ermöglichen es Ihnen, dies zu tun.

Sie können BeginCommit aufrufen, um den zurückgehaltenen Commit an einen Thread aus dem Threadpool weiterzuleiten. Sie können auch EndCommit aufrufen, um festzustellen, ob die Transaktion tatsächlich abgeschlossen wurde. Wenn die Transaktion aus irgendeinem Grund nicht ausgeführt werden konnte, EndCommit löst eine Transaktionsausnahme aus. Wenn die Transaktion zum Zeitpunkt EndCommit des Aufrufs noch nicht zugesichert wird, wird der Aufrufer blockiert, bis die Transaktion zugesichert oder abgebrochen wird.

Die einfachste Möglichkeit zum Ausführen eines asynchronen Commits besteht darin, eine Rückrufmethode bereitzustellen, die aufgerufen werden soll, wenn das Commit abgeschlossen ist. Sie müssen jedoch die EndCommit Methode für das ursprüngliche CommittableTransaction Objekt aufrufen, das zum Aufrufen des Aufrufs verwendet wird. Um dieses Objekt abzurufen, können Sie den Parameter IAsyncResult der Rückrufmethode heruntercasten, da die Klasse CommittableTransaction die Klasse IAsyncResult 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