Implementera en explicit transaktion med CommittableTransaction
Klassen CommittableTransaction ger ett explicit sätt för program att använda en transaktion, i stället för att använda TransactionScope klassen implicit. Det är användbart för program som vill använda samma transaktion över flera funktionsanrop eller flera trådanrop. TransactionScope Till skillnad från klassen måste programskrivaren specifikt anropa Commit metoderna och Rollback för att genomföra eller avbryta transaktionen.
Översikt över klassen CommittableTransaction
Klassen CommittableTransaction härleds från Transaction klassen och tillhandahåller därför alla funktioner i den senare. Specifikt användbar är Rollback metoden i Transaction klassen som också kan användas för att återställa ett CommittableTransaction objekt.
Klassen Transaction liknar CommittableTransaction klassen men erbjuder Commit
ingen metod. På så sätt kan du skicka transaktionsobjektet (eller klonerna av det) till andra metoder (eventuellt på andra trådar) samtidigt som du styr när transaktionen checkas in. Den anropade koden kan registrera och rösta på transaktionen, men endast objektets CommittableTransaction skapare har möjlighet att checka in transaktionen.
Observera följande när du arbetar med CommittableTransaction klassen,
Om du skapar en CommittableTransaction transaktion anges inte den omgivande transaktionen. Du måste specifikt ange och återställa den omgivande transaktionen för att säkerställa att resurshanterna fungerar i rätt transaktionskontext när det är lämpligt. Sättet att ange den aktuella omgivande transaktionen är genom att ange den statiska Current egenskapen för det globala Transaction objektet.
Det går inte att återanvända ett CommittableTransaction objekt. När ett CommittableTransaction objekt har checkats in eller återställts kan det inte användas igen i en transaktion. Det kan alltså inte anges som den aktuella omgivande transaktionskontexten.
Skapa en CommittableTransaction
Följande exempel skapar en ny CommittableTransaction och checkar in den.
//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
Att skapa en instans av CommittableTransaction anger inte automatiskt den omgivande transaktionskontexten. Därför är alla åtgärder på en resurshanterare inte en del av den transaktionen. Den statiska Current egenskapen för det globala Transaction objektet används för att ange eller hämta den omgivande transaktionen och programmet måste manuellt ange den för att säkerställa att resurshanterare kan delta i transaktionen. Det är också en bra idé att spara den gamla omgivande transaktionen och återställa den när du är klar med CommittableTransaction objektet.
För att genomföra transaktionen måste du uttryckligen Commit anropa metoden. För att återställa en transaktion bör du anropa Rollback metoden. Det är viktigt att observera att tills en CommittableTransaction har checkats in eller återställts är alla resurser som ingår i transaktionen fortfarande låsta.
Ett CommittableTransaction objekt kan användas mellan funktionsanrop och trådar. Det är dock upp till programutvecklaren att hantera undantag och specifikt anropa Rollback(Exception) metoden vid fel.
Asynkron incheckning
Klassen CommittableTransaction tillhandahåller också en mekanism för att genomföra en transaktion asynkront. En transaktionsincheckning kan ta mycket tid eftersom det kan innebära flera databasåtkomster och möjlig nätverksfördröjning. När du vill undvika dödlägen i program med högt dataflöde kan du använda asynkron incheckning för att slutföra transaktionsarbetet så snart som möjligt och köra incheckningsåtgärden som en bakgrundsaktivitet. Med BeginCommit klassmetoderna CommittableTransaction och EndCommit kan du göra det.
Du kan anropa BeginCommit för att skicka incheckningen till en tråd från trådpoolen. Du kan också anropa EndCommit för att avgöra om transaktionen faktiskt har checkats in. Om transaktionen inte kunde genomföras av någon anledning, EndCommit skapar ett transaktionsundatag. Om transaktionen ännu inte har checkats in när den EndCommit anropas blockeras anroparen tills transaktionen har checkats in eller avbrutits.
Det enklaste sättet att utföra en asynkron incheckning är genom att tillhandahålla en återanropsmetod som ska anropas när incheckningen är klar. Du måste dock anropa EndCommit metoden för det ursprungliga CommittableTransaction objektet som används för att anropa anropet. För att hämta objektet kan du nedarbeta parametern IAsyncResult för återanropsmetoden eftersom CommittableTransaction klassen implementerar IAsyncResult klassen.
I följande exempel visas hur en asynkron incheckning kan göras.
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
}
}