System.Transactions-integratie met SQL Server
.NET Framework versie 2.0 heeft een transactieframework geïntroduceerd dat toegankelijk is via de System.Transactions naamruimte. Dit framework maakt transacties beschikbaar op een manier die volledig is geïntegreerd in .NET Framework, met inbegrip van ADO.NET.
Naast de verbeteringen System.Transactions in de programmeerbaarheid en ADO.NET kunnen samenwerken om optimalisaties te coördineren wanneer u met transacties werkt. Een promotabele transactie is een lichtgewicht (lokale) transactie die automatisch kan worden gepromoveerd naar een volledig gedistribueerde transactie op basis van behoefte.
Vanaf ADO.NET 2.0 worden System.Data.SqlClient promotietransacties ondersteund wanneer u met SQL Server werkt. Een promotiebare transactie roept de toegevoegde overhead van een gedistribueerde transactie niet aan, tenzij de toegevoegde overhead vereist is. Promotable transacties zijn automatisch en vereisen geen tussenkomst van de ontwikkelaar.
Promotable-transacties zijn alleen beschikbaar wanneer u de .NET Framework-gegevensprovider voor SQL Server () met SQL Server (SqlClient
) gebruikt.
Promotable-transacties maken
De .NET Framework-provider voor SQL Server biedt ondersteuning voor promotietransacties, die worden verwerkt via de klassen in de .NET Framework-naamruimte System.Transactions . Promotable-transacties optimaliseren gedistribueerde transacties door het maken van een gedistribueerde transactie uit te stellen totdat deze nodig is. Als er slechts één Resource Manager vereist is, vindt er geen gedistribueerde transactie plaats.
Notitie
In een gedeeltelijk vertrouwd scenario is dit DistributedTransactionPermission vereist wanneer een transactie wordt gepromoveerd naar een gedistribueerde transactie.
Promotiescenario's voor transacties
Gedistribueerde transacties verbruiken doorgaans aanzienlijke systeemresources, die worden beheerd door Microsoft Distributed Transaction Coordinator (MS DTC), waarmee alle resourcemanagers die in de transactie zijn geopend, worden geïntegreerd. Een promotable transactie is een speciale vorm van een System.Transactions transactie die het werk effectief delegeert aan een eenvoudige SQL Server-transactie. System.Transactions, System.Data.SqlClienten SQL Server coördineren het werk dat betrokken is bij het afhandelen van de transactie, waarbij het naar behoefte wordt gepromoot naar een volledige gedistribueerde transactie.
Het voordeel van het gebruik van promotable transacties is dat wanneer een verbinding wordt geopend met behulp van een actieve TransactionScope transactie en er geen andere verbindingen worden geopend, de transactiedoorvoeringen als een lichtgewicht transactie in plaats van de extra overhead van een volledige gedistribueerde transactie te maken.
Trefwoorden voor verbindingsreeks
De ConnectionString eigenschap ondersteunt een trefwoord, Enlist
waarmee wordt aangegeven of System.Data.SqlClient transactionele contexten worden gedetecteerd en de verbinding automatisch wordt opgenomen in een gedistribueerde transactie. Als Enlist=true
de verbinding automatisch wordt opgenomen in de huidige transactiecontext van de openingsthread. Als Enlist=false
, de SqlClient
verbinding niet communiceert met een gedistribueerde transactie. De standaardwaarde is Enlist
waar. Als Enlist
deze niet is opgegeven in de verbindingsreeks, wordt de verbinding automatisch in een gedistribueerde transactie opgenomen als er een wordt gedetecteerd wanneer de verbinding wordt geopend.
De Transaction Binding
trefwoorden in een SqlConnection verbindingsreeks bepalen de koppeling van de verbinding met een in de lijst opgenomen System.Transactions
transactie. Het is ook beschikbaar via de TransactionBinding eigenschap van een SqlConnectionStringBuilder.
In de volgende tabel worden de mogelijke waarden beschreven.
Trefwoord | Beschrijving |
---|---|
Impliciete niet-bind | De standaardwaarde. De verbinding wordt losgekoppeld van de transactie wanneer deze eindigt, en schakelt terug naar de modus automatisch toewijzen. |
Expliciete koppeling ongedaan maken | De verbinding blijft gekoppeld aan de transactie totdat de transactie is gesloten. De verbinding mislukt als de gekoppelde transactie niet actief is of niet overeenkomt Current. |
TransactionScope gebruiken
De TransactionScope klasse maakt een codeblok transactioneel door impliciet verbindingen in te schakelen in een gedistribueerde transactie. U moet de Complete methode aan het einde van het blok aanroepen voordat u deze TransactionScope verlaat. Als u het blok verlaat, wordt de Dispose methode aangeroepen. Als er een uitzondering is gegenereerd waardoor de code het bereik verlaat, wordt de transactie beschouwd als afgebroken.
U wordt aangeraden een using
blok te gebruiken om ervoor te zorgen dat Dispose het TransactionScope object wordt aangeroepen wanneer het gebruiksblok wordt afgesloten. Fouten bij het doorvoeren of terugdraaien van transacties in behandeling kunnen de prestaties aanzienlijk beschadigen, omdat de standaardtime-out voor de transactie TransactionScope één minuut is. Als u geen using
instructie gebruikt, moet u alle werkzaamheden in een Try
blok uitvoeren en de Dispose methode in het Finally
blok expliciet aanroepen.
Als er een uitzondering optreedt in de TransactionScopetransactie, wordt de transactie gemarkeerd als inconsistent en wordt deze afgelaten. Het wordt teruggedraaid wanneer het TransactionScope wordt verwijderd. Als er geen uitzondering optreedt, voeren deelnemende transacties door.
Notitie
De TransactionScope
klasse maakt een transactie met een IsolationLevel standaardwaarde Serializable
. Afhankelijk van uw toepassing kunt u overwegen het isolatieniveau te verlagen om conflicten in uw toepassing te voorkomen.
Notitie
U wordt aangeraden alleen updates, invoegingen en verwijderingen uit te voeren binnen gedistribueerde transacties, omdat ze aanzienlijke databasebronnen verbruiken. Select-instructies kunnen databasebronnen onnodig vergrendelen. In sommige scenario's moet u mogelijk transacties gebruiken voor selecties. Alle niet-databasewerkzaamheden moeten buiten het bereik van de transactie worden uitgevoerd, tenzij er andere transacted resourcemanagers betrokken zijn. Hoewel een uitzondering in het bereik van de transactie voorkomt dat de transactie wordt doorgevoerd, heeft de TransactionScope klasse geen inrichting voor het terugdraaien van wijzigingen die uw code heeft aangebracht buiten het bereik van de transactie zelf. Als u actie moet ondernemen wanneer de transactie wordt teruggedraaid, moet u uw eigen implementatie van de IEnlistmentNotification interface schrijven en expliciet in de transactie opnemen.
Opmerking
Als u met System.Transactions deze functie werkt, hebt u een verwijzing naar System.Transactions.dll.
De volgende functie laat zien hoe u een promotiebare transactie maakt op twee verschillende SQL Server-exemplaren, vertegenwoordigd door twee verschillende SqlConnection objecten, die in een TransactionScope blok zijn verpakt. De code maakt het TransactionScope blok met een using
instructie en opent de eerste verbinding, die het automatisch in de TransactionScopelijst opgeeft. De transactie wordt in eerste instantie als een lichtgewicht transactie ingeschreven, niet als een volledige gedistribueerde transactie. De tweede verbinding wordt alleen opgenomen als TransactionScope de opdracht in de eerste verbinding geen uitzondering genereert. Wanneer de tweede verbinding wordt geopend, wordt de transactie automatisch gepromoveerd naar een volledige gedistribueerde transactie. De Complete methode wordt aangeroepen, waarmee de transactie alleen wordt doorgevoerd als er geen uitzonderingen zijn opgetreden. Als er op enig moment in het TransactionScope blok een uitzondering is opgetreden, Complete
wordt deze niet aangeroepen en wordt de gedistribueerde transactie teruggedraaid wanneer de TransactionScope transactie aan het einde van using
het blok wordt verwijderd.
// This function takes arguments for the 2 connection strings and commands in order
// to create a transaction involving two SQL Servers. It returns a value > 0 if the
// transaction committed, 0 if the transaction 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 RDBMS such as Oracle 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();
// Create the TransactionScope in which to execute the commands, guaranteeing
// that both commands will commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
try
{
// 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 by opening connection2
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
try
{
// The transaction is promoted 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);
}
catch (Exception ex)
{
// Display information that command2 failed.
writer.WriteLine("returnValue for command2: {0}", returnValue);
writer.WriteLine("Exception Message2: {0}", ex.Message);
}
}
catch (Exception ex)
{
// Display information that command1 failed.
writer.WriteLine("returnValue for command1: {0}", returnValue);
writer.WriteLine("Exception Message1: {0}", ex.Message);
}
}
// If an exception has been thrown, Complete will not
// be called and the transaction is rolled back.
scope.Complete();
}
// The returnValue is greater than 0 if the transaction committed.
if (returnValue > 0)
{
writer.WriteLine("Transaction was committed.");
}
else
{
// You could write additional business logic here, notify the caller by
// throwing a TransactionAbortedException, or log the failure.
writer.WriteLine("Transaction rolled back.");
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
' This function takes arguments for the 2 connection strings and commands in order
' to create a transaction involving two SQL Servers. It returns a value > 0 if the
' transaction committed, 0 if the transaction 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 RDBMS such as Oracle 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
' Create the TransactionScope in which to execute the commands, guaranteeing
' that both commands will commit or roll back as a single unit of work.
Using scope As New TransactionScope()
Using connection1 As New SqlConnection(connectString1)
Try
' 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 by opening connection2
' only when there is a chance that the transaction can commit.
Using connection2 As New SqlConnection(connectString2)
Try
' The transaction is promoted 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)
Catch ex As Exception
' Display information that command2 failed.
writer.WriteLine("returnValue for command2: {0}", returnValue)
writer.WriteLine("Exception Message2: {0}", ex.Message)
End Try
End Using
Catch ex As Exception
' Display information that command1 failed.
writer.WriteLine("returnValue for command1: {0}", returnValue)
writer.WriteLine("Exception Message1: {0}", ex.Message)
End Try
End Using
' If an exception has been thrown, Complete will
' not be called and the transaction is rolled back.
scope.Complete()
End Using
' The returnValue is greater than 0 if the transaction committed.
If returnValue > 0 Then
writer.WriteLine("Transaction was committed.")
Else
' You could write additional business logic here, notify the caller by
' throwing a TransactionAbortedException, or log the failure.
writer.WriteLine("Transaction rolled back.")
End If
' Display messages.
Console.WriteLine(writer.ToString())
Return returnValue
End Function