Implementera en implicit transaktion med transaktionsomfång
Klassen TransactionScope är ett enkelt sätt att markera ett kodblock som deltagande i en transaktion, utan att du behöver interagera med själva transaktionen. Ett transaktionsomfång kan välja och hantera den omgivande transaktionen automatiskt. På grund av dess användarvänlighet och effektivitet rekommenderar vi att du använder TransactionScope klassen när du utvecklar ett transaktionsprogram.
Dessutom behöver du inte uttryckligen registrera resurser med transaktionen. Alla System.Transactions resurshanterare (till exempel SQL Server 2005) kan identifiera förekomsten av en omgivande transaktion som skapats av omfånget och automatiskt registrera.
Skapa ett transaktionsomfång
Följande exempel visar en enkel användning av TransactionScope klassen.
// 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
Transaktionsomfånget startas när du har skapat ett nytt TransactionScope objekt. Som du ser i kodexemplet rekommenderar vi att du skapar omfång med en using
-instruktion. -instruktionen using
är tillgänglig både i C# och i Visual Basic och fungerar som ett try
...finally
-block för att säkerställa att omfånget tas bort korrekt.
När du instansierar TransactionScopeavgör transaktionshanteraren vilken transaktion som ska ingå. När det har fastställts deltar omfånget alltid i den transaktionen. Beslutet baseras på två faktorer: om det finns en omgivande transaktion och värdet för parametern TransactionScopeOption
i konstruktorn. Den omgivande transaktionen är den transaktion där koden körs. Du kan hämta en referens till den omgivande transaktionen genom att anropa klassens Transaction statiska Transaction.Current egenskap. Mer information om hur den här parametern används finns i avsnittet Hantera transaktionsflöde med TransactionScopeOption i det här avsnittet.
Slutföra ett transaktionsomfång
När programmet har slutfört allt arbete som det vill utföra i en transaktion bör du bara anropa TransactionScope.Complete metoden en gång för att informera transaktionshanteraren om att det är acceptabelt att genomföra transaktionen. Det är mycket bra att använda anropet som Complete den sista instruktionen using
i blocket.
Om du inte anropar den här metoden avbryts transaktionen eftersom transaktionshanteraren tolkar detta som ett systemfel eller motsvarar ett undantag som genereras inom transaktionens omfång. Att anropa den här metoden garanterar dock inte att transaktionen kommer att checkas in. Det är bara ett sätt att informera transaktionshanteraren om din status. När du har anropat Complete metoden kan du inte längre komma åt den omgivande transaktionen med hjälp Current av egenskapen, och om du försöker göra det genereras ett undantag.
TransactionScope Om objektet skapade transaktionen från början sker det faktiska arbetet med att genomföra transaktionen av transaktionshanteraren efter den sista kodraden using
i blocket. Om den inte skapade transaktionen sker incheckningen när Commit den anropas av objektets CommittableTransaction ägare. Då anropar transaktionshanteraren resurshanterarna och informerar dem om att antingen checka in eller återställa, baserat på om Complete metoden anropades för TransactionScope objektet.
- using
instruktionen Dispose säkerställer att -metoden för TransactionScope objektet anropas även om ett undantag inträffar. Metoden Dispose markerar slutet på transaktionsomfånget. Undantag som inträffar efter att den här metoden anropats kanske inte påverkar transaktionen. Den här metoden återställer även den omgivande transaktionen till det tidigare tillståndet.
En TransactionAbortedException genereras om omfånget skapar transaktionen och transaktionen avbryts. En TransactionInDoubtException genereras om transaktionshanteraren inte kan fatta ett incheckningsbeslut. Inget undantag utlöses om transaktionen har checkats in.
Återställa en transaktion
Om du vill återställa en transaktion bör du inte anropa Complete metoden inom transaktionsomfånget. Du kan till exempel utlösa ett undantag inom omfånget. Transaktionen som den deltar i kommer att återställas.
Hantera transaktionsflöde med TransactionScopeOption
Transaktionsomfång kan kapslas genom att anropa en metod som använder en TransactionScope inifrån en metod som använder sitt eget omfång, vilket är fallet med RootMethod
metoden i följande exempel,
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();
}
}
Det mest populära transaktionsomfånget kallas rotomfånget.
Klassen TransactionScope innehåller flera överlagrade konstruktorer som accepterar en uppräkning av typen TransactionScopeOption, som definierar transaktionsbeteendet för omfånget.
Ett TransactionScope objekt har tre alternativ:
Anslut den omgivande transaktionen eller skapa en ny om den inte finns.
Vara ett nytt rotomfång, det vill ex. starta en ny transaktion och låta den transaktionen vara den nya omgivande transaktionen inom sitt eget omfång.
Delta inte i en transaktion alls. Det finns därför ingen omgivande transaktion.
Om omfånget instansieras med Requiredoch en omgivande transaktion finns ansluter omfånget transaktionen. Om det å andra sidan inte finns någon omgivande transaktion skapar omfånget en ny transaktion och blir rotomfånget. Detta är standardvärdet. När Required används behöver koden i omfånget inte bete sig annorlunda oavsett om det är roten eller bara ansluter till den omgivande transaktionen. Den bör fungera identiskt i båda fallen.
Om omfånget instansieras med RequiresNewär det alltid rotomfånget. Den startar en ny transaktion och dess transaktion blir den nya omgivande transaktionen i omfånget.
Om omfånget instansieras med Suppressdeltar det aldrig i en transaktion, oavsett om det finns en omgivande transaktion. Ett omfång som instansieras med det här värdet har null
alltid som sin omgivande transaktion.
Alternativen ovan sammanfattas i följande tabell.
TransactionScopeOption | Omgivande transaktion | Omfånget deltar i |
---|---|---|
Obligatoriskt | Nej | Ny transaktion (kommer att vara roten) |
Kräver ny | Nej | Ny transaktion (kommer att vara roten) |
Suppress | Nej | Ingen transaktion |
Obligatoriskt | Ja | Omgivande transaktion |
Kräver ny | Ja | Ny transaktion (kommer att vara roten) |
Suppress | Ja | Ingen transaktion |
När ett TransactionScope objekt ansluter till en befintlig omgivande transaktion kan det hända att omfångsobjektet inte avslutar transaktionen, såvida inte omfånget avbryter transaktionen. Om den omgivande transaktionen skapades av ett rotomfång anropas Commit bara när rotomfånget tas bort för transaktionen. Om transaktionen skapades manuellt avslutas transaktionen när den antingen avbryts eller checkas in av skaparen.
I följande exempel visas ett TransactionScope objekt som skapar tre kapslade omfångsobjekt, var och en instansierad med ett annat TransactionScopeOption värde.
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))
{
//...
}
}
Exemplet visar ett kodblock utan någon omgivande transaktion som skapar ett nytt omfång (scope1
) med Required. Omfånget scope1
är ett rotomfång eftersom det skapar en ny transaktion (transaktion A) och gör Transaktion A till den omgivande transaktionen. Scope1
skapar sedan ytterligare tre objekt, var och en med ett annat TransactionScopeOption värde. Till exempel scope2
skapas med Required, och eftersom det finns en omgivande transaktion ansluter den den första transaktionen som skapats av scope1
. Observera att scope3
är rotomfånget för en ny transaktion och som scope4
inte har någon omgivande transaktion.
Även om standardvärdet och det vanligaste värdet TransactionScopeOption för är Requiredhar vart och ett av de andra värdena sitt unika syfte.
Icke-transaktionskod i ett transaktionsomfång
Suppress är användbart när du vill bevara de åtgärder som utförs av kodavsnittet och inte vill avbryta den omgivande transaktionen om åtgärderna misslyckas. Till exempel när du vill utföra loggnings- eller granskningsåtgärder, eller när du vill publicera händelser till prenumeranter oavsett om din omgivande transaktion checkar in eller avbryter. Med det här värdet kan du ha ett icke-transaktionellt kodavsnitt i ett transaktionsomfång, som du ser i följande exempel.
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
}
Röstning i ett kapslat omfång
Även om ett kapslat omfång kan ansluta till den omgivande transaktionen i rotomfånget har anrop Complete i det kapslade omfånget ingen effekt på rotomfånget. Transaktionen kommer endast att checkas in om alla omfång, från rotomfattningen ned till det senaste kapslade omfånget, röstar för att genomföra transaktionen. Om du inte anropar Complete i ett kapslat omfång påverkas rotomfånget eftersom den omgivande transaktionen omedelbart avbryts.
Ange tidsgränsen för TransactionScope
Vissa av de överbelastade konstruktorerna TransactionScope av accepterar ett värde av typen TimeSpan, som används för att styra tidsgränsen för transaktionen. En timeout inställd på noll innebär en oändlig timeout. Oändlig timeout är användbart främst för felsökning, när du vill isolera ett problem i affärslogiken genom att gå igenom koden, och du inte vill att den transaktion som du felsöker ska överskrida tidsgränsen när du försöker hitta problemet. Var mycket försiktig med att använda det oändliga timeout-värdet i alla andra fall, eftersom det åsidosätter skyddet mot transaktionslåsningar.
Du ställer vanligtvis in timeout till TransactionScope andra värden än standard i två fall. Den första är under utvecklingen, när du vill testa hur programmet hanterar avbrutna transaktioner. Genom att ange timeouten till ett litet värde (till exempel en millisekunder) får du transaktionen att misslyckas och kan därför observera felhanteringskoden. Det andra fallet där du anger att värdet ska vara mindre än standardtimeouten är när du tror att omfånget är involverat i resurskonkurrering, vilket resulterar i dödlägen. I så fall vill du avbryta transaktionen så snart som möjligt och inte vänta tills standardtimeouten upphör att gälla.
När ett omfång ansluter till en omgivande transaktion men anger en mindre timeout än den som den omgivande transaktionen är inställd på tillämpas den nya, kortare tidsgränsen TransactionScope på objektet och omfånget måste avslutas inom den angivna kapslade tiden eller så avbryts transaktionen automatiskt. Om tidsgränsen för det kapslade omfånget är mer än den omgivande transaktionens, har den ingen effekt.
Ange transactionscope-isoleringsnivån
Vissa överlagrade konstruktorer TransactionScope accepterar en struktur av typen TransactionOptions för att ange en isoleringsnivå, utöver ett timeout-värde. Som standard körs transaktionen med isoleringsnivån inställd på Serializable. Att välja en annan isoleringsnivå än Serializable används ofta för läsintensiva system. Detta kräver en gedigen förståelse för transaktionsbearbetningsteorin och semantiken för själva transaktionen, samtidighetsproblemen och konsekvenserna för systemkonsekvensen.
Dessutom stöder inte alla resurshanterare alla isoleringsnivåer, och de kan välja att delta i transaktionen på en högre nivå än den som konfigurerats.
Varje isoleringsnivå förutom Serializable är känslig för inkonsekvens till följd av andra transaktioner som har åtkomst till samma information. Skillnaden mellan de olika isoleringsnivåerna är hur läs- och skrivlås används. Ett lås kan bara hållas när transaktionen kommer åt data i resurshanteraren, eller så kan den hållas kvar tills transaktionen har checkats in eller avbrutits. Det förstnämnda är bättre för dataflöde, det senare för konsekvens. De två typerna av lås och de två typerna av åtgärder (läsning/skrivning) ger fyra grundläggande isoleringsnivåer. Mer information finns i IsolationLevel.
När du använder kapslade TransactionScope objekt måste alla kapslade omfång konfigureras för att använda exakt samma isoleringsnivå om de vill ansluta till den omgivande transaktionen. Om ett kapslat TransactionScope objekt försöker ansluta till den omgivande transaktionen men den anger en annan isoleringsnivå genereras en ArgumentException .
Interop med COM+
När du skapar en ny TransactionScope instans kan du använda EnterpriseServicesInteropOption uppräkningen i någon av konstruktorerna för att ange hur du ska interagera med COM+. Mer information om detta finns i Samverkan med Enterprise Services och COM+-transaktioner.