Partilhar via


Integração System.Transactions com o SQL Server

Aplica-se a: .NET Framework .NET .NET Standard

Baixar ADO.NET

.NET inclui uma estrutura de transações que pode ser acedida através do System.Transactions namespace. Este framework expõe as transações de uma forma totalmente integrada no .NET, incluindo ADO.NET.

Para além das melhorias de programabilidade, System.Transactions e ADO.NET podem trabalhar em conjunto para coordenar otimizações ao trabalhar com transações. Uma transação promocional é uma transação leve (local) que pode ser automaticamente promovida para uma transação totalmente distribuída conforme necessário.

O Microsoft SqlClient Data Provider para SQL Server suporta transações promocionáveis quando trabalha com SQL Server. Uma transação promocionável não invoca a sobrecarga adicional de uma transação distribuída, a menos que essa sobrecarga adicional seja necessária. As transações promocionáveis são automáticas e não requerem intervenção do promotor.

Criação de transações promocionáveis

O Microsoft SqlClient Data Provider para SQL Server oferece suporte para transações promocionáveis, que são tratadas através das classes no System.Transactions namespace. As transações promocionáveis otimizam as transações distribuídas adiando a criação de uma transação até que seja necessária. Se for necessário apenas um gestor de recursos, não ocorre qualquer transação distribuída.

Observação

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

Cenários de transação promocionáveis

As transações distribuídas normalmente consomem recursos significativos do sistema, sendo geridas pelo Microsoft Distributed Transaction Coordinator (MS DTC), que integra todos os gestores de recursos acedidos na transação. Uma transação promocionável é uma forma especial de System.Transactions transação que delega efetivamente o trabalho a uma simples transação do SQL Server. System.Transactions, Microsoft.Data.SqlClient, e SQL Server coordenam o trabalho envolvido no tratamento da transação, promovendo-a para uma transação totalmente distribuída conforme necessário.

A vantagem de usar transações promocionáveis é que, quando uma conexão é aberta através de uma transação ativa TransactionScope e nenhuma outra conexão é aberta, a transação confirma-se como uma transação leve, em vez de incorrer na sobrecarga adicional de uma transação totalmente distribuída.

Palavras-chave de cadeia de ligação

A ConnectionString propriedade suporta uma palavra-chave, Enlist, que indica se Microsoft.Data.SqlClient irá detetar contextos transacionais e registar automaticamente a ligação numa transação distribuída. Se Enlist=true, a ligação é automaticamente registada no contexto atual da transação do thread de abertura. Se Enlist=false, a SqlClient ligação não interage com uma transação distribuída. O valor padrão para Enlist é verdadeiro. Se Enlist não for especificado na cadeia de ligação, a ligação é automaticamente registada numa transação distribuída se for detetada quando a ligação é aberta.

As Transaction Binding palavras-chave numa SqlConnection cadeia de conexão controlam a associação da conexão a uma transação inscrita. Também está disponível através da TransactionBinding propriedade de um SqlConnectionStringBuilder.

A tabela seguinte descreve os valores possíveis.

Keyword Description
Desvinculação Implícita O padrão. A ligação desliga-se da transação quando esta termina, mudando novamente para o modo de autocommit.
Desvinculação Explícita A ligação mantém-se ligada à transação até que esta seja encerrada. A ligação falhará se a transação associada não estiver ativa ou não corresponderCurrent.

Utilização do TransactionScope

A TransactionScope classe torna um bloco de código transacional ao envolver implicitamente conexões numa transação distribuída. Deve chamar o método Complete no final do bloco TransactionScope antes de sair dele. Sair do bloco invoca o método Dispose. Se for lançada uma exceção que faz com que o código saia do âmbito, a transação é considerada abortada.

Recomendamos que use um using bloco para garantir que esse Dispose bloco é chamado ao TransactionScope objeto quando o bloco de utilização for fechado. A falha em confirmar ou reverter transações pendentes pode prejudicar significativamente o desempenho porque o tempo de espera padrão para o TransactionScope é de um minuto. Se não usar uma instrução using, deve realizar todo o trabalho dentro de um bloco Try e chamar explicitamente o método Dispose dentro do bloco Finally.

Se ocorrer uma exceção em TransactionScope, a transação é marcada como inconsistente e é abandonada. Será desfeito quando o TransactionScope for descartado. Se não houver exceção, as transações participantes são comprometidas.

Observação

A TransactionScope classe cria uma transação com um IsolationLevel de Serializable por padrão. Dependendo da sua aplicação, pode querer considerar baixar o nível de isolamento para evitar alta contenção na sua aplicação.

Observação

Recomendamos que realize apenas atualizações, inserções e eliminações dentro das transações distribuídas, pois consomem recursos significativos da base de dados. As instruções Select podem bloquear recursos da base de dados desnecessariamente e, em alguns cenários, poderá ter de usar transações para as seleções. Qualquer trabalho que não seja base de dados deve ser feito fora do âmbito da transação, a menos que envolva outros gestores de recursos transacionados. Embora uma exceção no âmbito da transação impeça que a transação seja concluída, a classe TransactionScope não tem provisão para anular quaisquer alterações feitas pelo seu código fora do escopo da própria transação. Se tiveres de tomar alguma ação quando a transação for revertida, tens de escrever a tua própria implementação da IEnlistmentNotification interface e inscrever-te explicitamente na transação.

Example

Trabalhar com System.Transactions exige que tenha uma referência a System.Transactions.dll.

A função seguinte demonstra como criar uma transação promotável contra duas instâncias diferentes do SQL Server, representadas por dois objetos distintos SqlConnection , que estão encapsulados num TransactionScope bloco.

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

A transação é inicialmente registada como uma transação leve, não como uma transação distribuída completa. A segunda ligação é adicionada à TransactionScope apenas se o comando na primeira ligação não lançar uma exceção. Quando a segunda ligação é aberta, a transação é automaticamente promovida para uma transação distribuída completa.

Mais tarde, o método Complete é invocado e efetua a transação apenas se não forem lançadas exceções. Se uma exceção tiver sido lançada em qualquer ponto do seu TransactionScope bloco, Complete não será chamada, e a transação distribuída reverterá quando o TransactionScope for descartado no final do seu using bloco.

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

Consulte também