Implémentation d'une transaction explicite à l'aide de CommittableTransaction

La classe CommittableTransaction permet aux applications d’utiliser une transaction de façon explicite au lieu d’utiliser la classe TransactionScope de façon implicite. Cela se révèle utile pour les applications qui souhaitent utiliser une même transaction pour plusieurs appels de fonction ou plusieurs appels de thread. Contrairement à la classe TransactionScope, le writer d’application doit absolument appeler les méthodes Commit et Rollback pour valider ou abandonner la transaction.

Vue d'ensemble de la classe CommittableTransaction

La classe CommittableTransaction est dérivée de la classe Transaction et offre donc toutes les fonctionnalités de cette classe. Particulièrement utile, la méthode Rollback de la classe Transaction permet également de restaurer un objet CommittableTransaction.

La classe Transaction est similaire à la classe CommittableTransaction mais ne fournit pas la méthode Commit. Cela permet de passer l'objet transaction (ou ses clones) à d'autres méthodes (potentiellement sur d'autres threads) tout en contrôlant l'instant de validation de la transaction. Le code appelé peut s'inscrire à la transaction et voter, mais seul le créateur de l'objet CommittableTransaction a la responsabilité de valider la transaction.

Si vous utilisez la classe CommittableTransaction, notez les éléments suivants :

  • La création d'une transaction CommittableTransaction ne définit pas la transaction ambiante. Vous devez définir et réinitialiser la transaction ambiante pour que le gestionnaire de ressources fonctionne sous le contexte de transaction approprié et de façon adaptée. Pour configurer la transaction ambiante en cours, affectez la propriété Current statique à l'objet Transaction global.

  • Un objet CommittableTransaction ne peut pas être réutilisé. Un objet CommittableTransaction qui a été validé ou restauré ne peut pas être réutilisé pour une transaction. Autrement dit, on ne peut pas l'utiliser en tant que contexte de la transaction ambiante en cours.

Création d'une CommittableTransaction

L'exemple suivant illustre la création et la validation d'une nouvelle CommittableTransaction.

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

La création d'une instance de CommittableTransaction n'entraîne pas automatiquement la définition du contexte de transaction ambiante. Par conséquent, les opérations effectuées sur un gestionnaire de ressources ne font pas partie de cette transaction. La propriété Current statique de l'objet Transaction global permet de définir ou de récupérer la transaction ambiante. Puis l'application doit la configurer manuellement pour s'assurer que le gestionnaire de ressources peut participer à la transaction. Il est également conseillé d'enregistrer l'ancienne transaction ambiante et de la restaurer après l'utilisation de l'objet CommittableTransaction.

Pour valider la transaction, vous devez explicitement appeler la méthode Commit. Pour restaurer une transaction, appelez la méthode Rollback. Il est important de noter que, jusqu'à la validation ou la restauration d'une CommittableTransaction, toutes les ressources concernées par la transaction sont verrouillées.

Un objet CommittableTransaction peut être utilisé pour plusieurs appels de fonction et threads. Toutefois, le développeur d'applications reste responsable de la gestion des exceptions et de l'appel à la méthode Rollback(Exception) en cas de défaillance.

Validation asynchrone

La classe CommittableTransaction fournit également un mécanisme de validation asynchrone des transactions. La validation d'une transaction peut prendre un moment, puisqu'elle peut impliquer l'accès à plusieurs bases de données et un temps de latence du réseau. Pour éviter les blocages des applications à haut débit, utilisez la validation asynchrone pour terminer le travail transactionnel aussi rapidement que possible et exécutez l'opération de validation en arrière-plan. Cela est possible grâce aux méthodes BeginCommit et EndCommit de la classe CommittableTransaction.

Vous pouvez appeler BeginCommit pour transmettre la suspension de la validation à un thread du pool de threads. Vous pouvez également appeler EndCommit pour contrôler si la transaction a bien été validée. Si la transaction n'a pas été validée, pour quelque raison que ce soit, EndCommit lève une exception de transaction. Si la transaction n'a toujours pas été validée lors de l'appel à EndCommit, l'appelant reste bloqué jusqu'à ce que la transaction soit validée ou abandonnée.

La méthode la plus simple pour réaliser une validation asynchrone consiste à fournir une méthode de rappel à appeler une fois la validation terminée. Vous devez toutefois appeler la méthode EndCommit sur l'objet CommittableTransaction d'origine utilisé pour l'appel. Pour obtenir cet objet, vous pouvez effectuer un downcast du paramètre IAsyncResult de la méthode de rappel, puisque la classe CommittableTransaction implémente la classe IAsyncResult.

L'exemple suivant illustre la réalisation d'une validation asynchrone.

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

Voir aussi