Condividi tramite


Implementazione di una transazione esplicita utilizzando CommittableTransaction

A differenza della classe CommittableTransaction, che consente di utilizzare le transazioni in modo implicito, la classe TransactionScope consente di utilizzare le transazioni in modo esplicito. Questa classe è utile nelle applicazioni che utilizzano la stessa transazione per più chiamate di funzione o di thread. A differenza della classe TransactionScope, il writer di applicazione deve chiamare in modo specifico i metodi Commit e Rollback, rispettivamente per interrompere la transazione o per eseguirne il commit.

Panoramica sulla classe CommittableTransaction

La classe CommittableTransaction deriva dalla classe Transaction e pertanto ne presenta tutte le funzionalità. Il metodo Rollback della classe Transaction è particolarmente utile e può inoltre essere utilizzato per eseguire il rollback di un oggetto CommittableTransaction.

La classe Transaction è simile alla classe CommittableTransaction, con la differenza che non offre un metodo Commit. Ciò consente di passare l'oggetto di transazione (o i cloni di tale oggetto) agli altri metodi (eventualmente presenti su altri thread) e al contempo controllare il momento in cui viene eseguito il commit della transazione. Benché il codice chiamato sia in grado di integrarsi nella transazione e di votare sull'esito di quest'ultima, soltanto il creatore dell'oggetto CommittableTransaction può eseguire il commit della transazione.

Quando si utilizza la classe CommittableTransaction è opportuno tenere presente i punti seguenti.

  • La creazione di una transazione CommittableTransaction non comporta l'impostazione della transazione di ambiente. Questa transazione deve essere impostata e reimpostata in modo specifico, in modo da garantire che i gestori di risorse possano funzionare nel contesto temporale e transazionale appropriato. Per definire la transazione di ambiente corrente è necessario impostare la proprietà statica Current dell'oggetto globale Transaction.

  • Gli oggetti CommittableTransaction non possono essere riutilizzati. In particolare, un oggetto CommittableTransaction per cui è stato eseguito il commit o il rollback non può essere utilizzato nuovamente in una transazione. In altre parole, non può essere impostato come contesto della transazione di ambiente corrente.

Creazione di una transazione CommittableTransaction

Nell'esempio seguente viene creata una nuova istanza della classe CommittableTransaction e quindi ne viene eseguito il commit.

//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 creazione di una transazione CommittableTransaction non comporta l'impostazione automatica del contesto della transazione di ambiente. Ne consegue che a tale transazione non può appartenere alcuna operazione di gestore di risorse. La proprietà statica Current dell'oggetto globale Transaction viene utilizzata per impostare o recuperare la transazione di ambiente e l'applicazione deve impostarla manualmente per garantire che i gestori di risorse possano partecipare alla transazione. È inoltre consigliabile salvare la transazione di ambiente precedente e ripristinarla al termine dell'utilizzo dell'oggetto CommittableTransaction.

Per eseguire il commit di una transazione è necessario chiamare esplicitamente il metodo Commit. Per eseguire il rollback di una transazione è invece necessario chiamare il metodo Rollback. È importante notare che tutte le risorse coinvolte in una transazione CommittableTransaction restano bloccate finché non viene eseguito il commit o il rollback di tale transazione.

Un oggetto CommittableTransaction può essere utilizzato per più thread e chiamate di funzione. In caso di errore, tuttavia, spetta allo sviluppatore dell'applicazione gestire le eccezioni e chiamare in modo specifico il metodo Rollback(Exception).

Commit asincrono

La classe CommittableTransaction fornisce inoltre un meccanismo per eseguire il commit asincrono di una transazione. Il commit di una transazione può richiedere molto tempo, in quanto può coinvolgere varie operazioni di accesso al database nonché un'eventuale latenza di rete. Nelle applicazioni ad elevata velocità effettiva è essenziale evitare i deadlock. A tale scopo, è possibile utilizzare il commit asincrono per concludere le operazioni transazionali il prima possibile ed eseguire l'operazione di commit come attività in background. I metodi BeginCommit e EndCommit della classe CommittableTransaction consentono di raggiungere tale obiettivo.

È possibile chiamare il metodo BeginCommit per avviare la procedura di commit in un thread del pool di thread. È inoltre possibile chiamare il metodo EndCommit per determinare se il commit della transazione è stato effettivamente eseguito. Se per qualche motivo il commit della transazione ha avuto esito negativo, il metodo EndCommit genera un'eccezione di transazione. Se al momento della chiamata al metodo EndCommit il commit della transazione non è ancora stato eseguito, il chiamante resta bloccato finché la transazione non viene interrotta o non ne viene eseguito il commit.

Il modo più semplice per eseguire un commit asincrono è ricorrere a un metodo callback che viene chiamato al completamento della procedura di commit. Tuttavia, è necessario chiamare il metodo EndCommit sull'oggetto CommittableTransaction inizialmente utilizzato per effettuare la chiamata. Per ottenere tale oggetto è possibile eseguire il downcast del parametro IAsyncResult del metodo di callback, in quanto la classe CommittableTransaction implementa la classe IAsyncResult.

L'esempio seguente illustra come eseguire un commit asincrono.

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

Vedi anche