Implementando uma transação explícita usando CommittableTransaction
A CommittableTransaction classe fornece uma maneira explícita para os aplicativos usarem uma transação, em vez de usar a TransactionScope classe implicitamente. É útil para aplicativos que desejam usar a mesma transação em várias chamadas de função ou várias chamadas de thread. Ao contrário da TransactionScope classe, o gravador do aplicativo precisa chamar especificamente os Commit métodos e Rollback para confirmar ou abortar a transação.
Visão geral da classe CommittableTransaction
A CommittableTransaction classe deriva da Transaction classe, portanto, fornecendo toda a funcionalidade desta última. Especificamente útil é o Rollback Transaction método na classe que também pode ser usado para reverter um CommittableTransaction objeto.
A Transaction classe é semelhante à classe, CommittableTransaction mas não oferece um Commit
método. Isso permite que você passe o objeto de transação (ou clones dele) para outros métodos (potencialmente em outros threads) enquanto ainda controla quando a transação é confirmada. O código chamado é capaz de se alistar e votar na transação, mas apenas o CommittableTransaction criador do objeto tem a capacidade de confirmar a transação.
Você deve observar o seguinte ao trabalhar com a CommittableTransaction classe,
Criar uma CommittableTransaction transação não define a transação ambiente. Você precisa definir e redefinir especificamente a transação de ambiente, para garantir que os gerentes de recursos operem sob o contexto de transação correto, quando apropriado. A maneira de definir a transação de ambiente atual é definindo a propriedade static Current no objeto global Transaction .
Um CommittableTransaction objeto não pode ser reutilizado. Depois que um CommittableTransaction objeto for confirmado ou revertido, ele não poderá ser usado novamente em uma transação. Ou seja, não pode ser definido como o contexto atual da transação ambiental.
Criando uma CommittableTransaction
O exemplo a seguir cria um novo CommittableTransaction e o confirma.
//Create a committable transaction
tx = new CommittableTransaction();
SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=northwind");
SqlCommand myCommand = new SqlCommand();
//Open the SQL connection
myConnection.Open();
//Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx);
myCommand.Connection = myConnection;
// Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)";
myCommand.ExecuteNonQuery();
// Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')";
myCommand.ExecuteNonQuery();
// Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')";
myCommand.ExecuteNonQuery();
// Commit or rollback the transaction
while (true)
{
Console.Write("Commit or Rollback? [C|R] ");
ConsoleKeyInfo c = Console.ReadKey();
Console.WriteLine();
if ((c.KeyChar == 'C') || (c.KeyChar == 'c'))
{
tx.Commit();
break;
}
else if ((c.KeyChar == 'R') || (c.KeyChar == 'r'))
{
tx.Rollback();
break;
}
}
myConnection.Close();
tx = null;
tx = New CommittableTransaction
Dim myConnection As New SqlConnection("server=(local)\SQLExpress;Integrated Security=SSPI;database=northwind")
Dim myCommand As New SqlCommand()
'Open the SQL connection
myConnection.Open()
'Give the transaction to SQL to enlist with
myConnection.EnlistTransaction(tx)
myCommand.Connection = myConnection
'Restore database to near it's original condition so sample will work correctly.
myCommand.CommandText = "DELETE FROM Region WHERE (RegionID = 100) OR (RegionID = 101)"
myCommand.ExecuteNonQuery()
'Insert the first record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (100, 'MidWestern')"
myCommand.ExecuteNonQuery()
'Insert the second record.
myCommand.CommandText = "Insert into Region (RegionID, RegionDescription) VALUES (101, 'MidEastern')"
myCommand.ExecuteNonQuery()
'Commit or rollback the transaction
Dim c As ConsoleKeyInfo
While (True)
Console.Write("Commit or Rollback? [C|R] ")
c = Console.ReadKey()
Console.WriteLine()
If (c.KeyChar = "C") Or (c.KeyChar = "c") Then
tx.Commit()
Exit While
ElseIf ((c.KeyChar = "R") Or (c.KeyChar = "r")) Then
tx.Rollback()
Exit While
End If
End While
myConnection.Close()
tx = Nothing
A criação de uma instância de não define automaticamente o contexto da CommittableTransaction transação ambiente. Portanto, qualquer operação em um gerenciador de recursos não faz parte dessa transação. A propriedade static Current no objeto global Transaction é usada para definir ou recuperar a transação de ambiente e o aplicativo deve defini-la manualmente para garantir que os gerentes de recursos possam participar da transação. Também é uma boa prática salvar a transação de ambiente antiga e restaurá-la quando terminar de usar o CommittableTransaction objeto.
Para confirmar a transação, você precisa chamar explicitamente o Commit método. Para reverter uma transação, você deve chamar o Rollback método. É importante notar que, até que um CommittableTransaction tenha sido confirmado ou revertido, todos os recursos envolvidos nessa transação ainda estão bloqueados.
Um CommittableTransaction objeto pode ser usado em chamadas de função e threads. No entanto, cabe ao desenvolvedor do aplicativo lidar com exceções e chamar especificamente o Rollback(Exception) método em caso de falhas.
Confirmação assíncrona
A CommittableTransaction classe também fornece um mecanismo para confirmar uma transação de forma assíncrona. Uma confirmação de transação pode levar tempo substancial, pois pode envolver acesso a vários bancos de dados e possível latência de rede. Quando quiser evitar bloqueios em aplicativos de alta taxa de transferência, você pode usar a confirmação assíncrona para concluir o trabalho transacional o mais rápido possível e executar a operação de confirmação como uma tarefa em segundo plano. Os BeginCommit e EndCommit métodos da CommittableTransaction classe permitem que você faça isso.
Você pode chamar BeginCommit para despachar a retenção de confirmação para um thread do pool de threads. Você também pode ligar EndCommit para determinar se a transação foi realmente confirmada. Se a transação não foi confirmada por qualquer motivo, EndCommit gera uma exceção de transação. Se a transação ainda não estiver confirmada no momento EndCommit em que for chamada, o chamador será bloqueado até que a transação seja confirmada ou abortada.
A maneira mais fácil de fazer uma confirmação assíncrona é fornecendo um método de retorno de chamada, a ser chamado quando a confirmação for concluída. No entanto, você deve chamar o EndCommit método no objeto original CommittableTransaction usado para invocar a chamada. Para obter esse objeto, você pode fazer downcast do parâmetro IAsyncResult do método de retorno de chamada, uma vez que a classe implementa a CommittableTransaction IAsyncResult classe.
O exemplo a seguir mostra como uma confirmação assíncrona pode ser feita.
public void DoTransactionalWork()
{
Transaction oldAmbient = Transaction.Current;
CommittableTransaction committableTransaction = new CommittableTransaction();
Transaction.Current = committableTransaction;
try
{
/* Perform transactional work here */
// No errors - commit transaction asynchronously
committableTransaction.BeginCommit(OnCommitted,null);
}
finally
{
//Restore the ambient transaction
Transaction.Current = oldAmbient;
}
}
void OnCommitted(IAsyncResult asyncResult)
{
CommittableTransaction committableTransaction;
committableTransaction = asyncResult as CommittableTransaction;
Debug.Assert(committableTransaction != null);
try
{
using(committableTransaction)
{
committableTransaction.EndCommit(asyncResult);
}
}
catch(TransactionException e)
{
//Handle the failure to commit
}
}