Partilhar via


Integração de System.Transactions com SQL Server

Aplicável a: .NET Framework .NET .NET Standard

Baixar ADO.NET

O .NET inclui uma estrutura de transação que pode ser acessada por meio do namespace System.Transactions. Essa estrutura expõe transações de uma forma totalmente integrada no .NET, incluindo o ADO.NET.

Além dos aprimoramentos de programação, o System.Transactions e o ADO.NET podem trabalhar em conjunto para coordenar otimizações quando você trabalha com transações. Uma transação passível de promoção é uma transação leve (local) que pode ser promovida automaticamente a uma transação totalmente distribuída, conforme necessário.

O Provedor de Dados Microsoft SqlClient para SQL Server dá suporte a transações passíveis de promoção quando você trabalha com o SQL Server. Uma transação passível de promoção não chama a sobrecarga adicional de uma transação distribuída a menos que a sobrecarga adicional seja necessária. As transações passíveis de promoção são automáticas e não exigem nenhuma intervenção do desenvolvedor.

Como criar transações passíveis de promoção

O Provedor de Dados Microsoft SqlClient para SQL Server fornece suporte para transações passíveis de promoção, que são tratadas por meio das classes no namespace System.Transactions. As transações passíveis de promoção otimizam as transações distribuídas por meio da criação de uma transação distribuída até que seja necessária. Se apenas um gerenciador de recursos for necessário, não ocorrerá nenhuma transação distribuída.

Observação

Em um cenário parcialmente confiável, a DistributedTransactionPermission é necessária quando uma transação é promovida para uma transação distribuída.

Cenários de transações passíveis de promoção

As transações distribuídas normalmente consomem recursos significativos do sistema, sendo gerenciadas pelo MS DTC (Coordenador de Transações Distribuídas da Microsoft), que integra todos os gerenciadores de recursos acessados na transação. Uma transação passível de promoção é uma forma especial de uma transação System.Transactions que delega efetivamente o trabalho para uma simples transação do SQL Server. O System.Transactions, o Microsoft.Data.SqlClient e 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 passíveis de promoção é que, quando uma conexão é aberta com uma transação TransactionScope ativa e não há nenhuma outra conexão aberta, a transação é confirmada como uma transação leve, em vez de gerar a sobrecarga adicional de uma transação distribuída completa.

Palavras-chave de cadeia de conexão

A propriedade ConnectionString dá suporte a uma palavra-chave, Enlist, que indica se Microsoft.Data.SqlClient detectará contextos transacionais e inscreverá automaticamente a conexão em uma transação distribuída. Se Enlist=true, a conexão será inscrita automaticamente no contexto de transação atual do thread de abertura. Se Enlist=false, a conexão SqlClient não interagirá com uma transação distribuída. O valor padrão de Enlist é true. Se Enlist não estiver especificado na cadeia de conexão, a conexão será inscrita automaticamente em uma transação distribuída, caso uma seja detectada no momento em que a conexão é aberta.

As palavras-chave Transaction Binding em uma cadeia de conexão SqlConnection controlam a associação da conexão com uma transação System.Transactions inscrita. Ela também está disponível por meio da propriedade TransactionBinding de um SqlConnectionStringBuilder.

A tabela a seguir descreve os valores possíveis.

Palavra-chave Descrição
Desassociação implícita O padrão. A conexão é desanexada da transação quando ela é encerrada, voltando para o modo de confirmação automática.
Desassociaçã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 a Current.

Como usar TransactionScope

Uma classe TransactionScope torna um bloco de código transacional inscrevendo implicitamente conexões em uma transação distribuída. Você precisará chamar o método Complete no final do bloco TransactionScope antes de deixá-lo. Deixar o bloco invoca o método Dispose. Se tiver sido gerada uma exceção que faz o código deixar o escopo, a transação será considerada anulada.

Recomendamos que você use um bloco using para garantir que Dispose seja chamado no objeto TransactionScope quando o bloco using for encerrado. Uma falha ao confirmar ou reverter transações pendentes pode diminuir muito o desempenho, pois o tempo limite padrão para o TransactionScope é de um minuto. Se você não usar uma instrução using, deverá executar todo o trabalho em um bloco Try e chamar explicitamente o método Dispose no bloco Finally.

Se ocorrer uma exceção no TransactionScope, a transação será marcada como inconsistente e abandonada. Ela será revertida quando o TransactionScope for descartado. Se nenhuma exceção ocorrer, as transações participantes serão confirmadas.

Observação

Por padrão, a classe TransactionScope cria uma transação com um IsolationLevel de Serializable. Dependendo do seu aplicativo, talvez você queira considerar abaixar o nível de isolamento para evitar uma contenção elevada em seu aplicativo.

Observação

Recomendamos executar apenas atualizações, inserções e exclusões em transações distribuídas, pois elas consomem recursos de banco de dados significativos. As instruções SELECT poderão bloquear recursos do banco de dados desnecessariamente e, em alguns cenários, você poderá precisar usar transações para seleções. Qualquer trabalho que não seja do banco de dados deve ser executado fora do escopo da transação, a menos que envolva outros gerenciadores de recursos transacionados. Apesar de uma exceção no escopo da transação evitar que a transação seja confirmada, a classe TransactionScope não tem provisão para reverter as alterações que o código tenha feito fora do escopo da própria transação. Se você precisar executar alguma ação ao reverter a ação, escreva sua implementação da interface IEnlistmentNotification e inscreva-se na transação explicitamente.

Exemplo

O uso de System.Transactions exige que você tenha uma referência a System.Transactions.dll.

A função a seguir demonstra como criar uma transação passível de promoção em duas instâncias diferentes do SQL Server, representadas por dois objetos SqlConnection diferentes, que são encapsulados em um bloco TransactionScope.

O código abaixo cria o bloco TransactionScope com uma instrução using e abre a primeira conexão, que o inscreve automaticamente no TransactionScope.

A transação é inscrita inicialmente como uma transação lightweight, não uma transação distribuída completa. A segunda conexão será inscrita no TransactionScope somente se o comando na primeira conexão não gerar uma exceção. Quando a segunda conexão é aberta, a transação é automaticamente promovida para uma transação distribuída completa.

Posteriormente, o método Complete é invocado, o que confirma a transação somente se nenhuma exceção é gerada. Se uma exceção for gerada em algum ponto no bloco TransactionScope, Complete não será chamado, e a transação distribuída será revertida quando o TransactionScope for descartado no final do bloco using.

using System;
using System.Transactions;
using Microsoft.Data.SqlClient;

class Program
{
    static void Main(string[] args)
    {
        string connectionString = "Data Source = localhost; Integrated Security = true; Initial Catalog = AdventureWorks";

        string commandText1 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong size')";
        string commandText2 = "INSERT INTO Production.ScrapReason(Name) VALUES('Wrong color')";

        int result = CreateTransactionScope(connectionString, connectionString, commandText1, commandText2);

        Console.WriteLine("result = " + result);
    }

    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;
    }
}

Confira também