Een impliciete transactie implementeren met behulp van transactiebereik
De TransactionScope klasse biedt een eenvoudige manier om een codeblok te markeren als deelnemer aan een transactie, zonder dat u hoeft te communiceren met de transactie zelf. Een transactiebereik kan de omgevingstransactie automatisch selecteren en beheren. Vanwege het gebruiksgemak en de efficiëntie wordt u aangeraden de klasse te gebruiken bij het TransactionScope ontwikkelen van een transactietoepassing.
Daarnaast hoeft u geen resources expliciet in te schakelen bij de transactie. Elke System.Transactions resourcemanager (zoals SQL Server 2005) kan het bestaan detecteren van een omgevingstransactie die door het bereik is gemaakt en automatisch wordt ingeschreven.
Een transactiebereik maken
In het volgende voorbeeld ziet u een eenvoudig gebruik van de 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
Het transactiebereik wordt gestart zodra u een nieuw TransactionScope object maakt. Zoals wordt geïllustreerd in het codevoorbeeld, is het raadzaam om bereiken te maken met een using
instructie. De using
instructie is zowel beschikbaar in C# als in Visual Basic en werkt als een try
...finally
blok om ervoor te zorgen dat het bereik correct wordt verwijderd.
Wanneer u een instantie maakt TransactionScope, bepaalt de transactiebeheerder aan welke transactie moet worden deelgenomen. Zodra dit is bepaald, neemt het bereik altijd deel aan die transactie. De beslissing is gebaseerd op twee factoren: of een omgevingstransactie aanwezig is en de waarde van de TransactionScopeOption
parameter in de constructor. De omgevingstransactie is de transactie waarin uw code wordt uitgevoerd. U kunt een verwijzing naar de omgevingstransactie verkrijgen door de statische Transaction.Current eigenschap van de Transaction klasse aan te roepen. Zie de sectie TransactionScopeOption van dit onderwerp voor meer informatie over hoe deze parameter wordt gebruikt.
Een transactiebereik voltooien
Wanneer uw toepassing al het werk voltooit dat het in een transactie wil uitvoeren, moet u de TransactionScope.Complete methode slechts eenmaal aanroepen om de transactiebeheerder te informeren dat het acceptabel is om de transactie door te voeren. Het is zeer goed om de oproep naar Complete te plaatsen als de laatste verklaring in het using
blok.
Als u deze methode niet aanroept, wordt de transactie afgebroken, omdat de transactiebeheerder dit interpreteert als een systeemfout of gelijkwaardig aan een uitzondering die is gegenereerd binnen het bereik van de transactie. Het aanroepen van deze methode garandeert echter niet dat de transactie wordt doorgevoerd. Het is slechts een manier om de transactiebeheerder te informeren over uw status. Nadat u de Complete methode hebt aangeroepen, hebt u geen toegang meer tot de omgevingstransactie met behulp van de Current eigenschap en wordt er een uitzondering gegenereerd.
Als het TransactionScope object de transactie in eerste instantie heeft gemaakt, vindt het werkelijke werk van het doorvoeren van de transactie door de transactiebeheerder plaats na de laatste coderegel in het using
blok. Als de transactie niet is gemaakt, vindt de doorvoering plaats wanneer Commit deze wordt aangeroepen door de eigenaar van het CommittableTransaction object. Op dat moment roept de transactiemanager de resourcemanagers aan en informeert deze over doorvoeren of terugdraaien, op basis van of de Complete methode op het TransactionScope object is aangeroepen.
De using
instructie zorgt ervoor dat de Dispose methode van het TransactionScope object wordt aangeroepen, zelfs als er een uitzondering optreedt. De Dispose methode markeert het einde van het transactiebereik. Uitzonderingen die optreden na het aanroepen van deze methode, hebben mogelijk geen invloed op de transactie. Met deze methode wordt ook de omgevingstransactie hersteld naar de vorige status.
Er wordt een TransactionAbortedException gegenereerd als het bereik de transactie maakt en de transactie wordt afgebroken. Er TransactionInDoubtException wordt een gegenereerd als de transactiebeheerder geen commit-beslissing kan bereiken. Er wordt geen uitzondering gegenereerd als de transactie wordt doorgevoerd.
Een transactie terugdraaien
Als u een transactie wilt terugdraaien, moet u de Complete methode niet binnen het transactiebereik aanroepen. U kunt bijvoorbeeld een uitzondering binnen het bereik genereren. De transactie waaraan de transactie deelneemt, wordt teruggedraaid.
Transactiestroom beheren met TransactionScopeOption
Transactiebereik kan worden genest door een methode aan te roepen die gebruikmaakt van een TransactionScope methode binnen een methode die een eigen bereik gebruikt, zoals het geval is met de RootMethod
methode in het volgende voorbeeld,
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();
}
}
Het meest recente transactiebereik wordt het hoofdbereik genoemd.
De TransactionScope klasse biedt verschillende overbelaste constructors die een opsomming van het type TransactionScopeOptionaccepteren, waarmee het transactionele gedrag van het bereik wordt gedefinieerd.
Een TransactionScope object heeft drie opties:
Neem deel aan de omgevingstransactie of maak een nieuwe als deze niet bestaat.
Wees een nieuw hoofdbereik, dat wil zeggen, start een nieuwe transactie en laat die transactie de nieuwe omgevingstransactie binnen een eigen bereik zijn.
Neem helemaal niet deel aan een transactie. Er is geen omgevingstransactie als gevolg hiervan.
Als het bereik wordt geïnstantieerd en Requireder een omgevingstransactie aanwezig is, wordt die transactie samengevoegd met het bereik. Als er echter geen omgevingstransactie is, maakt het bereik een nieuwe transactie en wordt het hoofdbereik. Dit is de standaardwaarde. Wanneer Required deze wordt gebruikt, hoeft de code binnen het bereik zich niet anders te gedragen, ongeacht of het de hoofdmap is of alleen maar deelneemt aan de omgevingstransactie. In beide gevallen moet het identiek functioneren.
Als het bereik wordt geïnstantieerd RequiresNew, is dit altijd het hoofdbereik. Er wordt een nieuwe transactie gestart en de transactie wordt de nieuwe omgevingstransactie binnen het bereik.
Als het bereik wordt geïnstantieerd Suppress, neemt het nooit deel aan een transactie, ongeacht of er een omgevingstransactie aanwezig is. Een bereik dat met deze waarde wordt geïnstantieerd, heeft null
altijd als omgevingstransactie.
De bovenstaande opties worden samengevat in de volgende tabel.
TransactionScopeOption | Omgevingstransactie | Het bereik neemt deel aan |
---|---|---|
Vereist | Nee | Nieuwe transactie (is de hoofdmap) |
Vereist nieuw | Nee | Nieuwe transactie (is de hoofdmap) |
Onderdrukken | Nee | Geen transactie |
Vereist | Ja | Omgevingstransactie |
Vereist nieuw | Ja | Nieuwe transactie (is de hoofdmap) |
Onderdrukken | Ja | Geen transactie |
Wanneer een TransactionScope object lid wordt van een bestaande omgevingstransactie, kan het verwijderen van het bereikobject de transactie niet beëindigen, tenzij het bereik de transactie afbreekt. Als de omgevingstransactie is gemaakt door een hoofdbereik, wordt Commit de transactie alleen aangeroepen wanneer het hoofdbereik wordt verwijderd. Als de transactie handmatig is gemaakt, eindigt de transactie wanneer deze wordt afgebroken of doorgevoerd door de maker ervan.
In het volgende voorbeeld ziet u een TransactionScope object dat drie geneste bereikobjecten maakt, die elk met een andere TransactionScopeOption waarde zijn geïnstantieerd.
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))
{
//...
}
}
In het voorbeeld ziet u een codeblok zonder omgevingstransactie waarmee een nieuw bereik (scope1
) Requiredwordt gemaakt. Het bereik scope1
is een hoofdbereik omdat het een nieuwe transactie (Transactie A) maakt en Transactie A de omgevingstransactie maakt. Scope1
maakt vervolgens nog drie objecten, elk met een andere TransactionScopeOption waarde. Wordt bijvoorbeeld scope2
gemaakt met Required, en omdat er een omgevingstransactie is, wordt de eerste transactie samengevoegd die is gemaakt door scope1
. Houd er rekening mee dat scope3
het hoofdbereik van een nieuwe transactie is en dat scope4
geen omgevingstransactie heeft.
Hoewel de standaardwaarde en de meest gebruikte waarde TransactionScopeOption is, heeft Requiredelk van de andere waarden het unieke doel.
Niet-transactionele code binnen een transactiebereik
Suppress is handig als u de bewerkingen die door de codesectie worden uitgevoerd, wilt behouden en de omgevingstransactie niet wilt afbreken als de bewerkingen mislukken. Als u bijvoorbeeld logboek- of controlebewerkingen wilt uitvoeren, of wanneer u gebeurtenissen naar abonnees wilt publiceren, ongeacht of uw omgevingstransactie doorvoert of afbreekt. Met deze waarde kunt u een sectie met niet-transactionele code binnen een transactiebereik hebben, zoals wordt weergegeven in het volgende voorbeeld.
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
}
Stemmen binnen een genest bereik
Hoewel een genest bereik kan worden samengevoegd met de omgevingstransactie van het hoofdbereik, heeft het aanroepen Complete in het geneste bereik geen effect op het hoofdbereik. De transactie wordt alleen doorgevoerd als alle bereiken, van het hoofdbereik tot het laatste geneste bereik, stemmen om de transactie door te voeren. Het niet aanroepen Complete van een genest bereik heeft invloed op het hoofdbereik, omdat de omgevingstransactie onmiddellijk wordt afgebroken.
Time-out voor TransactionScope instellen
Sommige overbelaste constructors accepteren TransactionScope een waarde van het type TimeSpan, die wordt gebruikt om de time-out van de transactie te beheren. Een time-out die op nul is ingesteld, betekent een oneindige time-out. Oneindige time-out is vooral handig voor foutopsporing, wanneer u een probleem in uw bedrijfslogica wilt isoleren door uw code te doorlopen en u niet wilt dat de transactie die u foutopsporing opspoort, een time-out optreedt tijdens het vinden van het probleem. Wees uiterst voorzichtig met het gebruik van de oneindige time-outwaarde in alle andere gevallen, omdat deze de beveiliging tegen transactie impasses overschrijft.
Meestal stelt u de TransactionScope time-out in op andere waarden dan standaard in twee gevallen. De eerste is tijdens de ontwikkeling, wanneer u wilt testen hoe uw toepassing afgebroken transacties verwerkt. Door de time-out in te stellen op een kleine waarde (zoals één milliseconde), zorgt u ervoor dat uw transactie mislukt en kunt u de code voor foutafhandeling dus observeren. In het tweede geval waarin u de waarde instelt op minder dan de standaardtime-out, is wanneer u denkt dat het bereik betrokken is bij resourceconflicten, wat resulteert in impasses. In dat geval wilt u de transactie zo snel mogelijk afbreken en niet wachten tot de standaardtime-out verloopt.
Wanneer een bereik wordt samengevoegd met een omgevingstransactie, maar een kleinere time-out opgeeft dan de time-out waarop de omgevingstransactie is ingesteld, wordt de nieuwe, kortere time-out afgedwongen op het TransactionScope object en moet het bereik eindigen binnen de opgegeven geneste tijd of wordt de transactie automatisch afgebroken. Als de time-out van het geneste bereik groter is dan die van de omgevingstransactie, heeft dit geen effect.
Het isolatieniveau transactionscope instellen
Sommige overbelaste constructors accepteren TransactionScope een structuur van het type TransactionOptions om een isolatieniveau op te geven, naast een time-outwaarde. De transactie wordt standaard uitgevoerd met isolatieniveau ingesteld op Serializable. Het selecteren van een ander isolatieniveau dan Serializable wordt vaak gebruikt voor leesintensieve systemen. Hiervoor is een solide kennis van de transactieverwerkingstheorie en de semantiek van de transactie zelf, de betrokken gelijktijdigheidsproblemen en de gevolgen voor systeemconsistentie vereist.
Bovendien ondersteunen niet alle resourcemanagers alle isolatieniveaus en kunnen ze ervoor kiezen om op een hoger niveau deel te nemen aan de transactie dan het niveau dat is geconfigureerd.
Elk isolatieniveau is bovendien Serializable vatbaar voor inconsistentie als gevolg van andere transacties die toegang hebben tot dezelfde informatie. Het verschil tussen de verschillende isolatieniveaus is de manier waarop lees- en schrijfvergrendelingen worden gebruikt. Een vergrendeling kan alleen worden bewaard wanneer de transactie toegang heeft tot de gegevens in de resourcemanager of kan worden bewaard totdat de transactie is doorgevoerd of afgebroken. De eerste is beter voor doorvoer, de laatste voor consistentie. De twee soorten vergrendelingen en de twee soorten bewerkingen (lezen/schrijven) geven vier basisisolatieniveaus. Zie IsolationLevel voor meer informatie.
Wanneer u geneste objecten gebruikt, moeten alle geneste TransactionScope bereiken worden geconfigureerd om precies hetzelfde isolatieniveau te gebruiken als ze de omgevingstransactie willen samenvoegen. Als een geneste TransactionScope object probeert deel te nemen aan de omgevingstransactie maar een ander isolatieniveau opgeeft, wordt er een ArgumentException gegenereerd.
Interoperabiliteit met COM+
Wanneer u een nieuw TransactionScope exemplaar maakt, kunt u de EnterpriseServicesInteropOption opsomming in een van de constructors gebruiken om op te geven hoe u met COM+werkt. Zie Interoperabiliteit met Enterprise Services en COM+-transacties voor meer informatie hierover.