Integração System.Transactions com o SQL Server
O .NET Framework versão 2.0 introduziu uma estrutura de transação que pode ser acessada por meio do System.Transactions namespace. Essa estrutura expõe transações de uma forma totalmente integrada no .NET Framework, incluindo ADO.NET.
Além dos aprimoramentos de programação, System.Transactions e ADO.NET podem trabalhar juntos para coordenar otimizações quando você trabalha com transações. Uma transação promocional é uma transação leve (local) que pode ser promovida automaticamente para uma transação totalmente distribuída conforme necessário.
A partir do ADO.NET 2.0, System.Data.SqlClient dá suporte a transações promocionais quando você trabalha com o SQL Server. Uma transação promocional não invoca a sobrecarga adicional de uma transação distribuída, a menos que a sobrecarga adicional seja necessária. As transações promocionais são automáticas e não requerem intervenção do desenvolvedor.
As transações promocionais só estão disponíveis quando você usa o Provedor de Dados do .NET Framework para SQL Server (SqlClient
) com o SQL Server.
Criação de transações promocionais
O Provedor do .NET Framework para SQL Server fornece suporte para transações promocionais, que são tratadas por meio das classes no namespace do .NET Framework System.Transactions . As transações promocionais otimizam as transações distribuídas, adiando a criação de uma transação distribuída até que ela seja necessária. Se apenas um gerenciador de recursos for necessário, nenhuma transação distribuída ocorrerá.
Nota
Em um cenário parcialmente confiável, o DistributedTransactionPermission é necessário quando uma transação é promovida para uma transação distribuída.
Cenários de transação promocionais
As transações distribuídas normalmente consomem recursos significativos do sistema, sendo gerenciadas pelo Microsoft Distributed Transaction Coordinator (MS DTC), que integra todos os gerenciadores de recursos acessados na transação. Uma transação promocional é uma forma especial de uma System.Transactions transação que efetivamente delega o trabalho a uma transação simples do SQL Server. System.Transactions, System.Data.SqlCliente o SQL Server coordenam o trabalho envolvido no tratamento da transação, promovendo-a para uma transação distribuída completa, conforme necessário.
A vantagem de usar transações promocionais é que, quando uma conexão é aberta usando uma transação ativa TransactionScope e nenhuma outra conexão é aberta, a transação é confirmada como uma transação leve, em vez de incorrer na sobrecarga adicional de uma transação distribuída completa.
Palavras-chave da cadeia de conexão
A ConnectionString propriedade suporta uma palavra-chave, Enlist
, que indica se System.Data.SqlClient detetará contextos transacionais e inscreverá automaticamente a conexão em uma transação distribuída. Se Enlist=true
, a conexão será automaticamente alistada no contexto de transação atual do thread de abertura. Se Enlist=false
, a SqlClient
conexão não interage com uma transação distribuída. O valor padrão para Enlist
é true. Se Enlist
não for especificado na cadeia de conexão, a conexão será automaticamente alistada em uma transação distribuída se uma for detetada quando a conexão for aberta.
As Transaction Binding
palavras-chave em uma SqlConnection cadeia de conexão controlam a associação da conexão com uma transação alistada System.Transactions
. Também está disponível através da TransactionBinding propriedade de um SqlConnectionStringBuilderarquivo .
A tabela a seguir descreve os valores possíveis.
Palavra-chave | Description |
---|---|
Desvinculação implícita | O padrão. A conexão se desprende da transação quando ela termina, voltando para o modo de confirmação automática. |
Desvinculação explícita | A conexão permanece anexada à transação até que a transação seja fechada. A conexão falhará se a transação associada não estiver ativa ou não corresponder Current. |
Usando o TransactionScope
A TransactionScope classe torna um bloco de código transacional inscrevendo implicitamente conexões em uma transação distribuída. Você deve chamar o Complete método no final do bloco antes de TransactionScope deixá-lo. Sair do bloco invoca o Dispose método. Se tiver sido lançada uma exceção que faça com que o código saia do escopo, a transação será considerada anulada.
Recomendamos que você use um using
bloco para garantir que Dispose ele seja chamado no TransactionScope objeto quando o bloco de uso for encerrado. A falha em confirmar ou reverter transações pendentes pode prejudicar significativamente o desempenho porque o tempo limite padrão para o TransactionScope é de um minuto. Se você não usar uma using
instrução, você deve executar todo o trabalho em um Try
bloco e chamar explicitamente o Dispose método no Finally
bloco .
Se ocorrer uma exceção no , a transação será marcada TransactionScopecomo inconsistente e será abandonada. Ele será revertido quando o TransactionScope for descartado. Se nenhuma exceção ocorrer, as transações participantes serão confirmadas.
Nota
A TransactionScope
classe cria uma transação com um IsolationLevel de Serializable
por padrão. Dependendo do seu aplicativo, convém considerar a redução do nível de isolamento para evitar alta contenção em seu aplicativo.
Nota
Recomendamos que você execute apenas atualizações, inserções e exclusões em transações distribuídas porque elas consomem recursos significativos do banco de dados. As instruções Select podem bloquear recursos de banco de dados desnecessariamente e, em alguns cenários, talvez seja necessário usar transações para seleções. Qualquer trabalho que não seja de banco de dados deve ser feito fora do escopo da transação, a menos que envolva outros gerentes de recursos transacionados. Embora uma exceção no escopo da transação impeça que a transação seja confirmada, a TransactionScope classe não tem provisão para reverter quaisquer alterações que seu código tenha feito fora do escopo da transação em si. Se você tiver que tomar alguma ação quando a transação for revertida, você deve escrever sua própria implementação da IEnlistmentNotification interface e se alistar explicitamente na transação.
Exemplo
Trabalhar com System.Transactions requer que você tenha uma referência a System.Transactions.dll.
A função a seguir demonstra como criar uma transação promocional em duas instâncias diferentes do SQL Server, representadas por dois objetos diferentes SqlConnection , que são encapsulados em um TransactionScope bloco. O código cria o TransactionScope bloco com uma using
instrução e abre a primeira conexão, que o inscreve automaticamente no TransactionScope. A transação é inicialmente listada como uma transação leve, não uma transação totalmente distribuída. A segunda conexão é alistada TransactionScope somente se o comando na primeira conexão não lançar uma exceção. Quando a segunda conexão é aberta, a transação é automaticamente promovida para uma transação totalmente distribuída. O Complete método é invocado, que confirma a transação somente se nenhuma exceção tiver sido lançada. Se uma exceção tiver sido lançada em qualquer ponto do TransactionScope bloco, Complete
não será chamada, e a transação distribuída será revertida quando a TransactionScope for descartada no final do bloco using
.
// 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