Implementar uma transação explícita usando CommittableTransaction

O CommittableTransaction classe fornece um modo explícito para os aplicativos que usam uma transação, em vez de usar o TransactionScope classe implicitamente. É útil para aplicativos que deseja usar a mesma transação em várias chamadas de função ou várias chamadas de threads. Ao contrário do TransactionScope classe, o criador do aplicativo precisa chamar especificamente o Commit e Rollback métodos para confirmar ou anular a transação.

Visão geral da classe CommittableTransaction

O CommittableTransaction classe deriva de Transaction classe, fornecendo, portanto, toda a funcionalidade do último. É especificamente útil a Rollback método o Transaction classe também pode ser usado para reverter uma CommittableTransaction objeto.

A classe Transaction é semelhante à classe de CommittableTransaction, mas não oferece um método Commit. 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 de chamada é capaz de se inscrever e votar na transação, mas somente o criador do CommittableTransaction objeto tem a capacidade de confirmar a transação.

Observe o seguinte ao trabalhar com o CommittableTransaction classe,

  • Criando um CommittableTransaction transação não define a transação de ambiente. Você precisa definir especificamente e redefinir a transação ambiente, para garantir que os gerenciadores de recursos operam sob o contexto de transação direita quando apropriado. A maneira de definir a transação de ambiente atual está definindo estático Current propriedade global Transaction objeto.

  • Um CommittableTransaction objeto não pode ser reutilizado. Uma vez um CommittableTransaction objeto foi confirmado ou revertido, ele não pode ser usado novamente em uma transação. Ou seja, ele não pode ser definido como o contexto de transação de ambiente atual.

Criando um CommittableTransaction

O exemplo a seguir cria um novo CommittableTransaction e confirma a ele.

//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

Criando uma instância de CommittableTransaction não define automaticamente o contexto de transação de ambiente. Portanto, qualquer operação em um Gerenciador de recursos não é parte da transação. Estático Current propriedade global Transaction objeto é usado para definir ou recuperar a transação de ambiente e o aplicativo deve defini-lo para garantir que os gerenciadores de recursos podem participar da transação manualmente. Também é uma boa prática para salvar a transação ambiente antiga e restaurá-lo 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 observar que até um CommittableTransaction foi confirmada ou revertida, 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, é o desenvolvedor de aplicativo para manipular exceções e chamar especificamente o Rollback(Exception) método em caso de falhas.

Confirmação assíncrona

O CommittableTransaction classe também fornece um mecanismo para confirmar uma transação de forma assíncrona. Uma confirmação de transação pode levar algum tempo, como ele pode envolver vários acesso de banco de dados e possível latência de rede. Quando você deseja evitar deadlocks em aplicativos de alta taxa de transferência, use confirmação assíncrona para concluir o trabalho transacional assim que possível e executar a operação de confirmação como uma tarefa em segundo plano. O BeginCommit e EndCommit métodos de CommittableTransaction classe permitem que você faça isso.

Você pode chamar BeginCommit para expedir a demora de confirmação a um thread do pool de threads. Você também pode chamar EndCommit para determinar se a transação, na verdade, foi confirmada. Se a transação não foi confirmada por algum motivo, EndCommit gera uma exceção de transação. Se a transação é ainda não foram confirmada no momento EndCommit é chamado, o chamador é bloqueado até que a transação é confirmada ou anulada.

É a maneira mais fácil de fazer uma confirmação assíncrona, fornecendo um método de retorno de chamada a ser chamada quando a confirmação for concluída. No entanto, você deve chamar o EndCommit método no original CommittableTransaction objeto usado para invocar a chamada. Para obter esse objeto, você pode baixar o parâmetro IAsyncResult do método de retorno de chamada, já que a classe CommittableTransaction implementa a classe IAsyncResult.

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

Confira também