La classe TransactionScope puo` essere utilizzata per contrassegnare facilmente un blocco di codice come partecipante a una transazione, senza che sia necessaria alcuna interazione con la transazione stessa. L'ambito della transazione consente di selezionare e gestire automaticamente la transazione di ambiente. La classe TransactionScope e` efficace e semplice da utilizzare ed e` quindi la soluzione ideale nello sviluppo di applicazioni basate su transazioni.
Inoltre, non e` necessario che le risorse vengano elencate esplicitamente con la transazione. Qualsiasi gestore di risorse System.Transactions, ad esempio SQL Server 2005, e` infatti in grado di rilevare l'esistenza di una transazione di ambiente creata dall'ambito e di inserirla automaticamente nell'elenco.
Creazione dell'ambito della transazione
Nell'esempio seguente viene illustrato un semplice utilizzo della classe TransactionScope.
Using scope As TransactionScope = New TransactionScope()
'Create and open the SQL connection. The work done on this connection will be a part of the transaction created by the TransactionScope
Dim myConnection As New SqlConnection("server=(local)\SQLExpress;Integrated Security=SSPI;database=northwind")
Dim myCommand As New SqlCommand()
myConnection.Open()
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()
myConnection.Close()
'Call complete on the TransactionScope or not based on input
Dim c As ConsoleKeyInfo
While (True)
Console.Write("Complete the transaction scope? [Y|N] ")
c = Console.ReadKey()
Console.WriteLine()
If (c.KeyChar = "Y") Or (c.KeyChar = "y") Then
scope.Complete()
Exit While
ElseIf ((c.KeyChar = "N") Or (c.KeyChar = "n")) Then
Exit While
End If
End While
End Using
using (TransactionScope ts = new TransactionScope())
{
//Create and open the SQL connection. The work done on this connection will be a part of the transaction created by the TransactionScope
SqlConnection myConnection = new SqlConnection("server=(local)\\SQLExpress;Integrated Security=SSPI;database=northwind");
SqlCommand myCommand = new SqlCommand();
myConnection.Open();
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();
myConnection.Close();
//Call complete on the TransactionScope or not based on input
ConsoleKeyInfo c;
while (true)
{
Console.Write("Complete the transaction scope? [Y|N] ");
c = Console.ReadKey();
Console.WriteLine();
if ((c.KeyChar == 'Y') || (c.KeyChar == 'y'))
{
// Commit the transaction
ts.Complete();
break;
}
else if ((c.KeyChar == 'N') || (c.KeyChar == 'n'))
{
break;
}
}
L'ambito della transazione viene definito dopo la creazione di un nuovo oggetto TransactionScope. Come illustrato nell'esempio di codice, per la creazione degli ambiti e` consigliabile l'utilizzo di un'istruzione using. Tale istruzione e` disponibile sia in C# che in Visual Basic e funziona come un blocco try...finally per assicurare la corretta eliminazione dell'ambito.
Quando viene creata un'istanza di TransactionScope, il gestore transazioni determina la transazione a cui partecipare. A questo punto, l'ambito partecipera` sempre a tale transazione. Per questa decisione vengono presi in considerazione due fattori, ovvero la presenza di una transazione di ambiente e il valore del parametro TransactionScopeOption nel costruttore. La transazione di ambiente e` la transazione all'interno della quale viene eseguito il codice. Per ottenere un riferimento alla transazione di ambiente e` possibile chiamare la proprieta` statica Current della classe Transaction. Per ulteriori informazioni sull'utilizzo di questo parametro, vedere la sezione Gestione di un flusso di transazioni mediante TransactionScopeOption.
Completamento dell'ambito della transazione
Dopo che l'applicazione ha completato tutte le operazioni richieste in una transazione, e` necessario chiamare il metodo Complete, una sola volta, per informare il gestore transazioni che e` possibile eseguire il commit della transazione. E` buona norma inserire la chiamata a Complete come ultima istruzione nel blocco using.
Se la chiamata a questo metodo non viene effettuata la transazione verra` annullata, poiche´ il gestore transazioni interpreta questa condizione come un errore di sistema, equivalente a un'eccezione generata all'interno dell'ambito della transazione. La chiamata a questo metodo, tuttavia, non garantisce il commit della transazione, ma e` semplicemente un modo per comunicare lo stato della transazione al gestore transazioni. Dopo la chiamata al metodo Complete, non e` piu` possibile accedere alla transazione di ambiente mediante la proprieta` Current. Se si tenta di eseguire questa operazione, verra` generata un'eccezione.
Se la transazione era stata creata inizialmente dall'oggetto TransactionScope, il commit effettivo della transazione da parte del gestore transazioni si verifichera` dopo l'esecuzione dell'ultima riga di codice nel blocco using. Se invece la transazione non era stata creata, il commit si verifichera` dopo la chiamata al metodo Commit da parte del proprietario dell'oggetto CommittableTransaction. A questo punto, il gestore transazioni effettua una chiamata ai gestori di risorse per informarli di eseguire il commit o il rollback, a seconda che sia stato chiamato o meno il metodo Complete sull'oggetto TransactionScope.
L'istruzione using assicura che il metodo Dispose dell'oggetto TransactionScope verra` chiamato anche se si verifica un'eccezione. Il metodo Dispose contrassegna la fine dell'ambito della transazione. Le eccezioni che si verificano dopo la chiamata a questo metodo non hanno alcun effetto sulla transazione. Questo metodo, inoltre, ripristina lo stato precedente della transazione di ambiente.
Se la transazione era stata creata dall'ambito, viene generata un'eccezione TransactionAbortedException e la transazione viene annullata. Se il gestore transazioni non riesce a raggiungere una decisione di commit, viene generata un'eccezione TransactionIndoubtException. In caso di commit della transazione, invece, non viene generata alcuna eccezione.
Rollback di una transazione
Se si desidera eseguire il rollback di una transazione, e` necessario che non venga effettuata una chiamata al metodo Complete nell'ambito della transazione. E` possibile, ad esempio, generare un'eccezione all'interno dell'ambito. Verra` eseguito il rollback della transazione a cui partecipa tale ambito.
Gestione di un flusso di transazioni mediante TransactionScopeOption
L'ambito della transazione puo` essere nidificato chiamando un metodo che utilizza un oggetto TransactionScope all'interno di un metodo che utilizza un proprio ambito, come nel metodo RootMethod dell'esempio seguente.
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();
}
}
L'ambito della transazione piu` esterna e` definito ambito principale.
Nella classe TransactionScope sono disponibili diversi costruttori in overload che accettano un'enumerazione del tipo TransactionScopeOption, che definisce il comportamento transazionale dell'ambito.
Per un oggetto TransactionScope sono disponibili tre opzioni:
-
Partecipare alla transazione di ambiente, oppure crearne una nuova nel caso non esista alcuna transazione di ambiente.
-
Costituire un nuovo ambito principale, ovvero avviare una nuova transazione e definirla come nuova transazione di ambiente all'interno del proprio ambito.
-
Non partecipare ad alcuna transazione. In questo caso, non sara` disponibile alcuna transazione di ambiente.
Se l'istanza dell'ambito e` stata creata con il valore Required ed e` presente una transazione di ambiente, l'ambito partecipera` a tale transazione. Se invece non esiste alcuna transazione di ambiente, verra` creata una nuova transazione che diventa l'ambito principale. Questo e` il valore predefinito. Quando viene utilizzato il valore Required, il codice all'interno dell'ambito non deve prevedere un comportamento differente a seconda che l'oggetto sia l'ambito principale o che stia semplicemente partecipando alla transazione di ambiente. Il comportamento del codice deve essere identico in entrambi i casi.
Se l'istanza dell'ambito e` stata creata con il valore RequiresNew, l'oggetto costituira` sempre l'ambito principale, ovvero verra` avviata una nuova transazione che diventera` la nuova transazione di ambiente all'interno dell'ambito.
Se l'istanza dell'ambito e` stata creata con il valore Suppress, l'oggetto non partecipera` mai a una transazione, indipendentemente dal fatto che sia presente o meno una transazione di ambiente. In questo caso, la transazione di ambiente di tale ambito sara` sempre null.
Nella tabella seguente sono elencate le opzioni descritte sopra.
|
TransactionScopeOption
|
Transazione di ambiente
|
Partecipazione dell'ambito
|
| Required | No | Nuova transazione (costituira` l'ambito principale) |
| RequiresNew | No | Nuova transazione (costituira` l'ambito principale) |
| Suppress | No | Nessuna transazione |
| Required | Si` | Transazione di ambiente |
| RequiresNew | Si` | Nuova transazione (costituira` l'ambito principale) |
| Suppress | Si` | Nessuna transazione |
Quando un oggetto TransactionScope partecipa a una transazione di ambiente esistente, l'eliminazione dell'oggetto ambito potrebbe non terminare la transazione, a meno che quest'ultima non venga annullata. Se la transazione di ambiente e` stata creata da un ambito principale, il metodo Commit verra` chiamato sulla transazione soltanto al momento dell'eliminazione dell'ambito. Se invece e` stata creata manualmente, la transazione terminera` quando viene annullata o viene eseguito il commit da parte del creatore.
Nell'esempio seguente viene illustrato un oggetto TransactionScope che crea tre oggetti ambito nidificati, per ognuno dei quali viene creata un'istanza con un valore TransactionScopeOption differente.
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))
{
...
}
}
Nell'esempio viene illustrato un blocco di codice senza alcuna transazione di ambiente in modo da creare un nuovo ambito (scope1) con il valore Required. L'ambito scope1 e` un ambito principale poiche´ crea una nuova transazione (Transazione A) e la imposta come transazione di ambiente. Scope1 crea quindi altri tre oggetti, ognuno con un valore TransactionScopeOption differente. Ad esempio, scope2 viene creato con Required e, poiche´ esiste una transazione di ambiente, partecipa alla prima transazione creata da scope1. Si noti che scope3 e` l'ambito principale di una nuova transazione e che per scope4 non esiste alcuna transazione di ambiente.
Sebbene il valore predefinito e piu` utilizzato di TransactionScopeOption sia Required, ognuno degli altri valori possiede uno scopo ben preciso.
Il valore Suppress e` utile quando si desidera mantenere le operazioni eseguite dalla sezione di codice senza annullare la transazione di ambiente in caso di errore durante tali operazioni, ad esempio quando si desidera eseguire operazioni di registrazione o controllo oppure quando si desidera pubblicare eventi per sottoscrittori indipendentemente dal fatto che la transazione di ambiente sia stata annullata o completata. Questo valore consente di utilizzare una sezione di codice non transazionale all'interno dell'ambito di una transazione, come illustrato nell'esempio seguente.
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
}
Votazione all'interno di un ambito nidificato
Anche se un ambito nidificato puo` partecipare alla transazione di ambiente dell'ambito principale, la chiamata al metodo Complete nell'ambito nidificato non ha alcun effetto sull'ambito principale. Il commit della transazione verra` eseguito solo se tutti gli ambiti compresi tra l'ambito principale e l'ultimo ambito nidificato votano per il commit della transazione.
Impostazione del timeout per TransactionScope
Alcuni dei costruttori in overload di TransactionScope accettano un valore di tipo TimeSpan, che viene utilizzato per controllare il timeout della transazione. Un timeout impostato su zero indica un timeout infinito. Questa impostazione e` utile soprattutto per il debug, quando si desidera isolare un problema nella regola business eseguendo il codice un'istruzione alla volta e non si desidera che si verifichi il timeout della transazione da controllare mentre si tenta di individuare il problema. In tutti gli altri casi si consiglia di utilizzare il valore di timeout infinito con la massima cautela, poiche´ questa impostazione causa l'annullamento dei sistemi di protezione che consentono di evitare i deadlock delle transazioni.
In genere, esistono due casi in cui il timeout di TransactionScope puo` essere impostato su valori diversi da quello predefinito. Il primo e` la fase di sviluppo, durante la quale si desidera testare il modo in cui l'applicazione gestisce le transazioni annullate. Se si imposta il timeout su un valore piccolo, ad esempio un millisecondo, la transazione non riuscira` e sara` quindi possibile esaminare il codice per la gestione degli errori. Il secondo caso che richiede l'impostazione di un timeout minore rispetto al valore predefinito e` quando si ritiene che l'ambito sia coinvolto in un problema di contesa di risorse, che potrebbe causare un deadlock. In tal caso, e` preferibile annullare la transazione il piu` presto possibile senza attendere la scadenza del timeout predefinito.
Quando un ambito partecipa a una transazione di ambiente ma specifica un valore di timeout piu` piccolo rispetto a quello impostato per la transazione di ambiente, sull'oggetto TransactionScope viene imposto il nuovo timeout piu` breve. L'ambito, inoltre, deve terminare entro il tempo specificato per l'ambito nidificato. In caso contrario, la transazione verra` annullata automaticamente. Se il timeout dell'ambito nidificato e` maggiore del timeout della transazione di ambiente, non verra` eseguita alcuna operazione.
Impostazione del livello di isolamento di TransactionScope
Oltre al valore di timeout, alcuni dei costruttori in overload di TransactionScope accettano una struttura di tipo TransactionOptions per specificare un livello di isolamento. Per impostazione predefinita, la transazione viene eseguita con il livello di isolamento Serializable. La scelta di un livello di isolamento diverso da Serializable e` in genere appropriata per i sistemi in cui e` prevista l'esecuzione di un numero elevato di operazioni di lettura. In questo caso e` necessaria una profonda conoscenza della teoria di elaborazione delle transazioni e della semantica della transazione stessa, dei possibili problemi di concorrenza e delle eventuali conseguenze a livello di coerenza dei sistemi.
Inoltre, poiche´ non tutti i gestori di risorse supportano tutti i livelli di isolamento, e` possibile che uno di essi decida di partecipare alla transazione a un livello piu` alto rispetto a quello configurato.
Ogni livello di isolamento diverso da Serializable potrebbe generare problemi di incoerenza a causa di altre transazioni che accedono alle stesse informazioni. La differenza tra i diversi livelli di isolamento riguarda il modo in cui vengono utilizzati i blocchi in lettura e scrittura. Un blocco puo` essere mantenuto solo quando la transazione accede ai dati nel gestore di risorse oppure fino a quando la transazione non viene completata o annullata. La prima condizione consente di migliorare le prestazioni, la seconda assicura un livello maggiore di coerenza. Utilizzando i due tipi di blocchi e i due tipi di operazioni (lettura/scrittura) e` possibile definire quattro livelli di isolamento di base. Per ulteriori informazioni, vedere IsolationLevel.
Quando si utilizzano oggetti TransactionScope nidificati, per poter partecipare alla transazione di ambiente e` necessario che tutti gli ambiti nidificati siano configurati in modo da utilizzare esattamente lo stesso livello di isolamento. Se un oggetto TransactionScope nidificato tenta di partecipare alla transazione di ambiente anche se utilizza un livello di isolamento differente, verra` generata un'eccezione ArgumentException.
Interoperabilita` con COM+
Vedere anche