Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Die TransactionScope Klasse bietet eine einfache Möglichkeit, einen Codeblock als Teilnahme an einer Transaktion zu markieren, ohne dass Sie mit der Transaktion selbst interagieren müssen. Ein Transaktionsbereich kann die Ambient-Transaktion automatisch auswählen und verwalten. Aufgrund seiner benutzerfreundlichkeit und Effizienz wird empfohlen, die TransactionScope Klasse beim Entwickeln einer Transaktionsanwendung zu verwenden.
Darüber hinaus müssen Sie keine Ressourcen explizit mit der Transaktion auflisten. 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
Das folgende Beispiel zeigt eine 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.
static public int CreateTransactionScope(
string connectString1, string connectString2,
string commandText1, string commandText2)
{
// Initialize the return value to zero and create a StringWriter to display results.
int returnValue = 0;
System.IO.StringWriter writer = 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 (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = 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.
SqlCommand command1 = 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 (SqlConnection connection2 = 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;
SqlCommand command2 = new SqlCommand(commandText2, connection2);
returnValue = command2.ExecuteNonQuery();
writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
}
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
' 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)
End Try
' Display messages.
Console.WriteLine(writer.ToString())
Return returnValue
End Function
Der Transaktionsbereich wird gestartet, nachdem Sie ein neues TransactionScope Objekt erstellt haben. Wie im Codebeispiel dargestellt, wird empfohlen, Bereiche mittels einer using-Anweisung zu erstellen. 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.
Wenn Sie instanziieren TransactionScope, bestimmt der Transaktionsmanager, an welcher Transaktion teilnehmen soll. Sobald er festgelegt wurde, ist der Bereich immer an dieser Transaktion beteiligt. Die Entscheidung basiert auf zwei Faktoren: ob eine Ambient-Transaktion vorhanden ist und der Wert des TransactionScopeOption Parameters im Konstruktor. Die Ambient-Transaktion ist die Transaktion, in der Ihr Code ausgeführt wird. Sie können einen Verweis auf die Umgebungstransaktion abrufen, indem Sie die statische Transaction.Current Eigenschaft der Transaction Klasse aufrufen. Weitere Informationen zur Verwendung dieses Parameters finden Sie im Abschnitt "Managing transaction flow using TransactionScopeOption " in diesem Thema.
Vervollständigen eines Transaktionsbereichs
Wenn Ihre Anwendung alle Aufgaben abgeschlossen hat, die sie in einer Transaktion ausführen möchte, sollten Sie die TransactionScope.Complete Methode nur einmal aufrufen, um den Transaktionsmanager darüber zu informieren, dass es akzeptabel ist, die Transaktion zu übernehmen. Der Aufruf von Complete wird üblicherweise als letzte Anweisung in den using-Block eingefügt.
Wenn diese Methode nicht aufgerufen wird, wird die Transaktion abgebrochen, da der Transaktionsmanager dies als Systemfehler interpretiert oder einer Ausnahme entspricht, die im Rahmen der Transaktion ausgelöst wird. Das Aufrufen dieser Methode garantiert jedoch nicht, dass die Transaktion abgeschlossen wird. Es ist lediglich eine Möglichkeit, den Transaktionsmanager über Ihren 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 anfangs erstellt hat, tritt die tatsächliche Arbeit des Commits der Transaktion durch den Transaktions-Manager nach der letzten Codezeile im using Block auf. Wenn die Transaktion nicht erstellt wurde, erfolgt das Commit immer dann, wenn Commit vom Besitzer des CommittableTransaction-Objekts aufgerufen wird. An diesem Punkt ruft der Transaktionsmanager die Ressourcenmanager auf und informiert sie, entweder commit oder rollback durchzuführen, je nachdem, ob die Complete Methode für das TransactionScope Objekt aufgerufen wurde.
Die using Anweisung stellt sicher, dass die Dispose Methode des TransactionScope Objekts aufgerufen wird, auch wenn eine Ausnahme auftritt. Die Dispose Methode markiert das Ende des Transaktionsbereichs. Ausnahmen, die nach dem Aufrufen dieser Methode auftreten, wirken sich möglicherweise nicht auf die Transaktion aus. Mit dieser Methode wird auch die Umgebungstransaktion in den vorherigen Zustand wiederhergestellt.
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.
Ausführen eines Rollbacks für eine Transaktion
Wenn Sie eine Transaktion zurücksetzen möchten, sollten Sie die Complete Methode nicht innerhalb des Transaktionsbereichs aufrufen. Zum Beispiel können Sie eine Ausnahme im Bereich auslösen. Die Transaktion, an der sie teilnimmt, wird zurückgesetzt.
Verwalten des Transaktionsflusses mithilfe von 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 stellt mehrere überladene Konstruktoren bereit, die eine Aufzählung des Typs TransactionScopeOptionakzeptieren, die das Transaktionsverhalten des Bereichs definiert.
Ein TransactionScope Objekt verfügt über drei Optionen:
Treten Sie der aktuellen Transaktion bei oder erstellen Sie eine neue, wenn noch keine existiert.
Definieren eines neuen Stammbereichs, d. h. Starten einer neuen Transaktion und Festlegen dieser Transaktion als neue Ambient-Transaktion in ihrem eigenem Bereich.
Keine Teilnahme 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. Wenn andererseits keine Umgebungstransaktion vorhanden ist, 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. Sie sollte in beiden Fällen identisch funktionieren.
Wenn der Bereich mit RequiresNewinstanziiert 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 festgelegt.
Die oben genannten Optionen sind in der folgenden Tabelle zusammengefasst.
| TransactionScopeOption | Ambient-Transaktion | Der Bereich ist beteiligt an |
|---|---|---|
| Erforderlich | Nein | Neue Transaktion (wird zum Stamm) |
| Erfordert "Neu" | Nein | Neue Transaktion (wird zum Stamm) |
| Unterdrücken | Nein | Keine Transaktion |
| Erforderlich | Ja | Ambient-Transaktion |
| Erfordert "Neu" | Ja | Neue Transaktion (wird zum Stamm) |
| Unterdrücken | Ja | Keine Transaktion |
Wenn ein TransactionScope Objekt einer vorhandenen Umgebungstransaktion beitritt, kann das Freigeben des Scope-Objekts die Transaktion möglicherweise nicht beenden, es sei denn, der Scope bricht die Transaktion ab. 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.
Das folgende Beispiel zeigt ein TransactionScope Objekt, das drei geschachtelte 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))
{
//...
}
}
Das Beispiel zeigt einen Codeblock, ohne dass eine Umgebungstransaktion einen neuen Bereich (scope1) mit Requirederstellt. Der Bereich scope1 ist ein Stammbereich, da er eine neue Transaktion (Transaktion A) erstellt und Transaktion A zur Umgebungstransaktion macht.
Scope1 anschließend werden drei weitere Objekte erstellt, die jeweils einen anderen TransactionScopeOption Wert aufweisen. Beispielsweise wird scope2 mit Required erstellt, und da es eine Umgebungstransaktion gibt, schließt er sich der ersten von scope1 erstellten Transaktion an. Beachten Sie, dass scope3 der Stammbereich einer neuen Transaktion ist und dass für scope4 keine Umgebungs-Transaktion vorhanden ist.
Obwohl der Standardwert und der am häufigsten verwendete Wert TransactionScopeOption ist Required, hat jeder der anderen Werte seinen eindeutigen Zweck.
Nicht transaktionsbezogener Code innerhalb eines Transaktionsbereichs
Suppress ist nützlich, wenn Sie die vom Codeabschnitt ausgeführten Vorgänge beibehalten möchten und die Umgebungstransaktion nicht abbrechen möchten, wenn die Vorgänge fehlschlagen. 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. Mit diesem Wert können Sie einen Nichttransaktionscodeabschnitt innerhalb eines Transaktionsbereichs verwenden, wie im folgenden Beispiel gezeigt.
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 geschachtelter Bereich die Umgebungstransaktion des Stammbereichs verbinden kann, hat der Aufruf Complete im geschachtelten Bereich keine Auswirkungen auf den Stammbereich. Die Transaktion wird nur dann abgeschlossen, wenn alle Bereiche, vom Stammbereich bis zum letzten geschachtelten Bereich, dem Commit der Transaktion zustimmen. Wenn Complete nicht in einem geschachtelten Bereich aufgerufen wird, wirkt sich dies auf den Stammbereich aus, da die Ambient-Transaktion sofort abgebrochen wird.
Festlegen des TransactionScope-Timeouts
Einige der überladenen Konstruktoren des TransactionScope Typs akzeptieren einen Wert vom Typ TimeSpan, der zum Steuern des Timeouts der Transaktion verwendet wird. Ein Timeout, das auf Null festgelegt ist, bedeutet 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. Seien Sie äußerst vorsichtig, indem Sie den unendlichen Timeoutwert in allen anderen Fällen verwenden, da sie die Schutzmaßnahmen gegen Transaktions-Deadlocks außer Kraft setzt.
In der Regel legen Sie das TransactionScope Timeout auf andere Werte als die Standardeinstellung in zwei Fällen fest. Die erste ist während der Entwicklung, wenn Sie testen möchten, wie Ihre Anwendung abgebrochene Transaktionen verarbeitet. Durch Festlegen des Timeouts auf einen kleinen Wert (z. B. eine Millisekunden) führen Sie dazu, dass ihre Transaktion fehlschlägt und so den Fehlerbehandlungscode beobachten kann. Der zweite Fall, in dem Sie den Wert auf einen Wert niedriger als das Standard-Timeout festlegen, ist, wenn Sie glauben, dass der Bereich an einem Ressourcenkonflikt beteiligt ist, der zu Deadlocks führt. In diesem Fall möchten Sie die Transaktion so schnell wie möglich abbrechen und nicht warten, bis das Standardtimeout abläuft.
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-Isolationsebene
Einige der überladenen Konstruktoren des TransactionScope Typs akzeptieren eine Struktur des Typs TransactionOptions , um eine Isolationsstufe zusätzlich zu einem Timeoutwert anzugeben. Standardmäßig wird die Transaktion ausgeführt, wobei die Isolationsstufe auf . Serializable festgelegt ist. Die Wahl einer anderen Isolationsstufe als Serializable ist bei leseintensiven Systemen üblich. Dies erfordert ein solides Verständnis der Transaktionsverarbeitungstheorie und der Semantik der Transaktion selbst, der Parallelitätsprobleme und der Folgen für die Systemkonsistenz.
Darüber hinaus unterstützen nicht alle Ressourcenmanager alle Isolationsebenen, und sie können sich entscheiden, an der Transaktion auf einer höheren Ebene teilzunehmen als die konfigurierte.
Alle Isolationsstufen außer Serializable sind anfällig für Inkonsistenzen, die sich aus dem Zugriff anderer Transaktionen auf dieselben Informationen ergeben. Der Unterschied zwischen den verschiedenen Isolationsstufen besteht darin, wie Lese- und Schreibsperren verwendet werden. Eine Sperre wird nur gehalten, wenn die Transaktion auf die Daten im Ressourcen-Manager zugreift, oder bleibt gesperrt bis die Transaktion bestätigt oder abgebrochen wird. Der erste ist besser für den Durchsatz, letzteres für Konsistenz. Die beiden Arten von Sperren und die beiden Arten von Vorgängen (Lese-/Schreibzugriff) bieten vier grundlegende Isolationsstufen. Weitere Informationen finden Sie unter IsolationLevel.
Bei Verwendung geschachtelter Objekte müssen alle geschachtelten TransactionScope Bereiche so konfiguriert werden, dass genau die gleiche Isolationsebene verwendet wird, wenn sie der Umgebungstransaktion beitreten möchten. Wenn ein geschachteltes TransactionScope Objekt versucht, der Umgebungstransaktion beizutreten, aber es eine andere Isolationsstufe angibt, wird eine ArgumentException ausgelöst.
Interoperabilität mit COM+
Wenn Sie eine neue TransactionScope Instanz erstellen, können Sie die EnterpriseServicesInteropOption Enumeration in einem der Konstruktoren verwenden, um anzugeben, wie mit COM+ interagiert werden soll. Weitere Informationen hierzu finden Sie unter "Interoperabilität mit Enterprise Services" und "COM+-Transaktionen".