Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
A TransactionScope classe fornece uma maneira simples de marcar um bloco de código como participando de uma transação, sem exigir que você interaja com a transação em si. Selecione um escopo de transação e gerenciar a transação ambiente automaticamente. Devido à facilidade de uso e eficiência, é recomendável que você use a TransactionScope classe ao desenvolver um aplicativo de transação.
Além disso, você não precisa conseguir recursos explicitamente com a transação. Qualquer System.Transactions gerenciador de recursos (como o SQL Server 2005) pode detectar a existência de uma transação ambiente criada pelo escopo e se inscrever automaticamente.
Criar um escopo de transação
O exemplo a seguir mostra um uso simples da TransactionScope classe.
// This function takes arguments for 2 connection strings and commands to create a transaction
// involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
// transaction is 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 3rd party RDBMS 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();
try
{
// Create the TransactionScope to execute the commands, guaranteeing
// that both commands can commit or roll back as a single unit of work.
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection1 = new SqlConnection(connectString1))
{
// 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 as connection2 is opened
// only when there is a chance that the transaction can commit.
using (SqlConnection connection2 = new SqlConnection(connectString2))
{
// The transaction is escalated 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);
}
}
// The Complete method commits the transaction. If an exception has been thrown,
// Complete is not called and the transaction is rolled back.
scope.Complete();
}
}
catch (TransactionAbortedException ex)
{
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
}
// Display messages.
Console.WriteLine(writer.ToString());
return returnValue;
}
' This function takes arguments for 2 connection strings and commands to create a transaction
' involving two SQL Servers. It returns a value > 0 if the transaction is committed, 0 if the
' transaction is 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 3rd party RDBMS
' 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
Try
' Create the TransactionScope to execute the commands, guaranteeing
' that both commands can commit or roll back as a single unit of work.
Using scope As New TransactionScope()
Using connection1 As New SqlConnection(connectString1)
' 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 as connection2 is opened
' only when there is a chance that the transaction can commit.
Using connection2 As New SqlConnection(connectString2)
' The transaction is escalated 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)
End Using
End Using
' The Complete method commits the transaction. If an exception has been thrown,
' Complete is called and the transaction is rolled back.
scope.Complete()
End Using
Catch ex As TransactionAbortedException
writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message)
End Try
' Display messages.
Console.WriteLine(writer.ToString())
Return returnValue
End Function
O escopo da transação é iniciado depois que você cria um novo TransactionScope objeto. Conforme ilustrado no exemplo de código, é recomendável que você crie escopos com a instrução using. A instrução using está disponível em C# e no Visual Basic, e funciona como um bloco try…finally para garantir que o escopo é descartado corretamente.
Quando você cria uma instância TransactionScope, o gerenciador de transações determina em qual transação participar. Uma vez determinado, o escopo sempre participa dessa transação. A decisão se baseia em dois fatores: se houver uma transação de ambiente e o valor de TransactionScopeOption parâmetro no construtor. A transação de ambiente é a transação na qual o seu código é executado. Você pode obter uma referência para a transação de ambiente chamando estático Transaction.Current propriedade o Transaction classe. Para obter mais informações sobre como esse parâmetro é usado, consulte a seção Gerenciando o fluxo de transações usando TransactionScopeOption deste tópico.
Concluindo um escopo de transação
Quando o aplicativo concluir todo o trabalho que deseja executar em uma transação, você deverá chamar o TransactionScope.Complete método apenas uma vez para informar ao gerenciador de transações que é aceitável confirmar a transação. É muito bom colocar a chamada para Complete como a última instrução no bloco using.
A falha ao chamar esse método anula a transação, pois o gerenciador de transações interpreta isso como uma falha do sistema ou equivalente a uma exceção gerada dentro do escopo da transação. No entanto, chamar esse método não garante que a transação será confirmada. É apenas uma maneira de informar o gerenciador de transações do seu status. Depois de chamar o Complete método, você não poderá mais acessar a transação ambiente usando a Current propriedade, e qualquer tentativa de fazer isso resultará em uma exceção.
Se o TransactionScope objeto criou a transação inicialmente, o trabalho real de confirmar a transação pelo gerenciador de transações ocorrerá após a última linha de código no using bloco. Se não tiver criado a transação, a confirmação ocorrerá sempre que Commit for chamada pelo proprietário do CommittableTransaction objeto. Nesse ponto, o gerenciador de transações chama os gerenciadores de recursos e os informa para confirmar ou reverter, com base no fato de que o método Complete foi chamado no objeto TransactionScope.
A using instrução garante que o Dispose método do TransactionScope objeto seja chamado mesmo se ocorrer uma exceção. O Dispose método marca o final do escopo da transação. Exceções que ocorrem depois de chamar esse método podem não afetar a transação. Esse método também restaura a transação ambiente ao estado anterior.
Um TransactionAbortedException será lançada se o escopo cria a transação e a transação for anulada. Um TransactionInDoubtException será gerado se o gerenciador de transações não conseguir chegar a uma decisão de Confirmação. Nenhuma exceção é gerada se a transação for confirmada.
Revertendo uma transação
Se você quiser reverter uma transação, não deverá chamar o Complete método dentro do escopo da transação. Por exemplo, você pode lançar uma exceção dentro do escopo. A transação na qual ela participa será revertida.
Gerenciando o fluxo de transações usando TransactionScopeOption
Escopo da transação pode ser aninhado, chamando um método que usa um TransactionScope de dentro de um método que usa seu próprio escopo, como é o caso com o RootMethod método no exemplo a seguir,
void RootMethod()
{
using(TransactionScope scope = new TransactionScope())
{
/* Perform transactional work here */
SomeMethod();
scope.Complete();
}
}
void SomeMethod()
{
using(TransactionScope scope = new TransactionScope())
{
/* Perform transactional work here */
scope.Complete();
}
}
O escopo de transação mais alto é conhecido como o escopo raiz.
A TransactionScope classe fornece vários construtores sobrecarregados que aceitam uma enumeração do tipo TransactionScopeOption, que define o comportamento transacional do escopo.
Um TransactionScope objeto tem três opções:
Participe da transação ambiental ou crie uma nova, caso não exista.
Ser um novo escopo de raiz, ou seja, iniciar uma nova transação e ter essa transação com a nova transação ambiente dentro de seu próprio escopo.
Não participar de uma transação de modo algum. Como resultado, há uma transação de ambiente.
Se o escopo for instanciado com Required, e uma transação ambiente estiver presente, o escopo unirá essa transação. Se, por outro lado, não houver transação ambiental, o escopo criará uma nova transação e se tornará o escopo raiz. Esse é o valor padrão. Quando Required é usado, o código dentro do escopo não precisa ter um comportamento diferente seja a raiz ou apenas associando a transação de ambiente. Ele deve operar de forma idêntica em ambos os casos.
Se o escopo for instanciado com RequiresNew, ele será sempre o escopo raiz. Ele inicia uma nova transação, e sua transação se torna a nova transação ambiente dentro do escopo.
Se o escopo for instanciado com Suppress, ele nunca participará de uma transação, independentemente de uma transação ambiente estar presente. Um escopo instanciado com esse valor sempre tem null como transação ambiente.
As opções acima são resumidas na tabela a seguir.
| TransactionScopeOption | Transação de ambiente | O escopo faz parte do |
|---|---|---|
| Obrigatório | Não | Nova transação (será a raiz) |
| Requer Novo | Não | Nova transação (será a raiz) |
| Suprimir | Não | Nenhuma transação |
| Obrigatório | Sim | Transação de ambiente |
| Requer Novo | Sim | Nova transação (será a raiz) |
| Suprimir | Sim | Nenhuma transação |
Quando um TransactionScope objeto ingressa em uma transação ambiente existente, descarte o objeto de escopo não pode terminar a transação, a menos que o escopo anula a transação. Se a transação ambiente criada por um escopo de raiz, somente quando o escopo raiz é descartado, não Commit chamado na transação. Se a transação foi criada manualmente, a transação termina quando ele é anulado ou confirmado pelo seu criador.
O exemplo a seguir mostra um objeto TransactionScope que cria três objetos de escopo aninhados, cada um instanciado com um valor TransactionScopeOption diferente.
using(TransactionScope scope1 = new TransactionScope())
//Default is Required
{
using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required))
{
//...
}
using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew))
{
//...
}
using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
{
//...
}
}
O exemplo mostra um bloco de código sem qualquer transação ambiente criando um novo escopo (scope1) com Required. O escopo scope1 é um escopo raiz, pois cria uma nova transação (Transação A) e torna a Transação A a transação ambiente.
Scope1 em seguida, cria mais três objetos, cada um com um valor diferente TransactionScopeOption . Por exemplo, scope2 é criado com Required, e como há uma transação de ambiente, ele une a primeira transação criada por scope1. Observe que esse scope3 é o escopo raiz de uma nova transação e que scope4 não tem nenhuma transação ambiente.
Embora o valor TransactionScopeOption padrão e mais comumente usado seja Required, cada um dos outros valores tem sua finalidade exclusiva.
Código não transacional dentro de um escopo de transação
Suppress é útil quando você deseja preservar as operações executadas pela seção de código e não deseja anular a transação de ambiente se as operações falharem. Por exemplo, quando você deseja executar um log ou operações de auditoria ou quando desejar publicar eventos para assinantes, independentemente de se a transação de ambiente confirma ou anula. Esse valor permite que você tenha uma seção de código não transacional dentro de um escopo de transação, conforme mostrado no exemplo a seguir.
using(TransactionScope scope1 = new TransactionScope())
{
try
{
//Start of non-transactional section
using(TransactionScope scope2 = new
TransactionScope(TransactionScopeOption.Suppress))
{
//Do non-transactional work here
}
//Restores ambient transaction here
}
catch {}
//Rest of scope1
}
Votação dentro de um escopo aninhado
Embora um escopo aninhado pode unir a transação de ambiente do escopo raiz, chamar Complete no escopo aninhado não tem nenhum efeito sobre o escopo raiz. A transação só será confirmada se todos os escopos, desde o escopo raiz até o último escopo aninhado, votarem para confirmar a transação. Não chamar Complete em um escopo aninhado afetará o escopo raiz como a transação ambiente imediatamente será anulada.
Definindo o tempo limite do TransactionScope
Alguns dos construtores sobrecarregados de TransactionScope aceitar um valor do tipo TimeSpan, que é usado para controlar o tempo limite da transação. Um tempo limite definido como zero significa um tempo limite infinito. Tempo limite infinito é útil principalmente para depuração, quando quiser isolar um problema em sua lógica de negócios, percorrendo seu código, e você não deseja que a transação que você depurar tempo limite durante a tentativa de localizar o problema. Tenha muito cuidado ao usar o valor de tempo limite infinito em todos os outros casos, pois ele substitui as proteções contra deadlocks de transação.
Normalmente, você define o TransactionScope tempo limite para valores diferentes do padrão em dois casos. A primeira é durante o desenvolvimento, quando você deseja testar a maneira como seu aplicativo lida com transações anuladas. Ao definir o tempo limite como um valor pequeno (como um milissegundo), você faz com que a transação falhe e, portanto, pode observar o código de tratamento de erros. O segundo caso em que você define o valor para ser menor que o tempo limite padrão é quando você acredita que o escopo está envolvido em contenção de recursos, resultando em impasses. Nesse caso, você deseja anular a transação assim que possível e não aguardar o tempo limite padrão expirar.
Quando um escopo une uma transação ambiente, mas especifica um tempo limite menor do que aquele para o qual a transação ambiente está definida, o novo tempo limite mais curto é imposto no TransactionScope objeto e o escopo deve terminar dentro do tempo aninhado especificado ou a transação é anulada automaticamente. Se o tempo limite do escopo aninhado é maior do que a transação de ambiente, ele não tem efeito.
Definindo o nível de isolamento TransactionScope
Alguns dos construtores sobrecarregados de TransactionScope aceitar uma estrutura do tipo TransactionOptions para especificar um nível de isolamento, além de um valor de tempo limite. Por padrão, a transação é executada com o nível de isolamento definido como Serializable. A seleção de um nível de isolamento diferente de Serializable normalmente é usado para sistemas com uso intensivo de leitura. Isso requer uma compreensão sólida da teoria do processamento de transações e da semântica da transação em si, dos problemas de simultaneidade envolvidos e das consequências para a consistência do sistema.
Além disso, nem todos os gerenciadores de recursos dão suporte a todos os níveis de isolamento e podem optar por participar da transação em um nível mais alto do que o configurado.
Cada nível de isolamento além Serializable disso é suscetível à inconsistência resultante de outras transações que acessam as mesmas informações. A diferença entre os diferentes níveis de isolamento está na maneira como os bloqueios de leitura e gravação são usados. Um bloqueio só pode ser mantido quando a transação acessa os dados no gerenciador de recursos ou pode ser mantido até que a transação seja confirmada ou anulada. O primeiro é melhor taxa de transferência, o segundo para manter a consistência. Os dois tipos de bloqueios e os dois tipos de operações (leitura/gravação) fornecem quatro níveis básicos de isolamento. Consulte IsolationLevel para obter mais informações.
Quando usando aninhadas TransactionScope objetos, todos os escopos aninhados devem ser configurados para usar exatamente o mesmo nível de isolamento se deseja unir a transação de ambiente. Se um aninhada TransactionScope objeto tentar unir a transação ambiente ainda Especifica um nível de isolamento diferente, um ArgumentException é lançada.
Interoperabilidade com COM+
Ao criar uma nova TransactionScope instância, você pode usar a EnterpriseServicesInteropOption enumeração em um dos construtores para especificar como interagir com COM+. Para obter mais informações sobre isso, consulte Interoperabilidade com serviços empresariais e transações COM+.