Nell'ultimo mio post "Alla scoperta di System.Transactions ... " avevo illustrato un HelloWorld del namespace "System.Transactions". L'impressione che forse dava il post e l'impressione - forse errata - con cui ho approcciato l'argomento era quella di poter introdurre sistemi transazionali con leggeranza come semplici e usuabili appaio le classi del nuovo namespace. E' stato un commento/domanda di (luka) a farmi aprire gli occhi spingendomi ad andare più a fondo nelle cose. Ecco di seguito illustrati i miei test, osservazioni e considerazioni sull'argomento.
Prima cosa - indipendente dall'osservazione tecnica di (luka) ma validi per qualsiasi ambiente e/o tecnologia transazionale - non credo sia buona cosa introdurre con semplicità la transazionalità in un ambiente non preposto per esserlo. Come si parla di componenti "Thread-Safe" per indicare quelli che supportano ambienti multithread si dovrebbe anche parlare di componenti "Transactions-Safe" per inidicare quelli che supportano ambienti transazionali e/o sono consci di poter girare correttamente in ambientre transazionale. Per capire questa mia affermazioni provate a fare quello che ho fatto io. 1) Inserite nei metodi del nostro Table1Manager delle Trace per notificare "begin" e "completed" dei suoi metodi. 2) Implementate un TraceListener custom che scriva la trace su database, il TraceListerner non dovrà conoscere System.Transactions... per cui NON sarà "Transactions-Safe". 3) Mettete in ascolto il Listener (Trace.Listeners.Add(new SqlLogTraceListener());). Si potrà facilmente notare che la trace di un TransactionScope non completato (scope.Complete();) non sarà permanente sul database (ma subirà una rollback)... e questo credo di poter dire sia male ai fini della diagnostica :(
Segue l'esempio di SqlLogTraceListener con cui ho fatto i test. Nell'esempio verrà creata un TransactionScope che esplicitamente vuole stare fuori da ogni esistente transazione in modo da garantire un corretto tracing. Sia chiaro non voglio dire che essere "Transactions-Safe" significa eslicitare il proprio comportamento nei riguardi della corrente transazione... ma significa essere consapevoli che se eseguiti in ambiente transazionale il componente/la classe svolgere correttamente/coerentemente il proprio compito... per esempio ritengo che Table1Manager si posso condiderare "Transactions-Safe" pur apparentemente ignorando la presenza di un ambiente transazionale. Quindi approcciare ad un ambiente transazionale significa chiedersi: tutti componenti che userò continueranno a funzionare correttamente/corentemente?
class SqlLogTraceListener: TraceListener
{
/// CREATE TABLE [LogTable] (
/// [Time] [datetime] NOT NULL DEFAULT (getdate()),
/// [Category] [varchar] (15) NOT NULL ,
/// [Note] [varchar] (300) NOT NULL
/// ) ON [PRIMARY]
string connectionString = "...";
public override void WriteLine(string message)
{
InsertIntoLogTable(message, string.Empty);
}
public override void Write(string message)
{
InsertIntoLogTable(message, string.Empty);
}
public override void Write(string message, string category)
{
InsertIntoLogTable(message, category);
}
public override void WriteLine(string message, string category)
{
InsertIntoLogTable(message, category);
}
private void InsertIntoLogTable(string message, string category)
{
//InsertIntoLogTableRaw(message, category); //Metodo "NON Transactions-safe"
InsertIntoLogSuppressTransaction(message, category); //Metodo "Transactions-Safe"
}
private void InsertIntoLogSuppressTransaction(string message, string category)
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Suppress))
{
InsertIntoLogTableRaw(message, category);
scope.Complete();
}
}
private void InsertIntoLogTableRaw(string message, string category)
{
SqlConnection connection = new SqlConnection(connectionString);
try
{
connection.Open();
SqlCommand command = new SqlCommand("INSERT INTO LogTable (Category, Note) VALUES (@Category, @Note)");
command.CommandType = CommandType.Text;
command.Connection = connection;
SqlParameter categoryParameter = new SqlParameter("@Category", SqlDbType.VarChar, 15);
categoryParameter.Value = category;
command.Parameters.Add(categoryParameter);
SqlParameter noteParameter = new SqlParameter("@Note", SqlDbType.VarChar, 300);
noteParameter.Value = message;
command.Parameters.Add(noteParameter);
command.ExecuteNonQuery();
}
catch
{
//TODO: Implementare gestione errore.
}
finally
{
connection.Dispose();
}
}
}
Veniamo ora alla domanda di (luka): ma sono ancora costretto a passare in giro la connessione xKè se le connessioni sono 2 (anche con medesima stringa di connessione sul medesimo DB) entra automaticamente in gioco il peeeesante OleTx?
Premesso che LTM è il nuovo gestore di architetture transazionali (passatemi il termine) che si contrappone al classico OleTx (che si basa su DTC, Distribuited Transaction Coordonator) mi associo a (luka) dicendo che il nuovo gestore ha ancora i suoi limiti. Limiti che infatti si possono vedere sia dal punto di vista tecnologico, ad oggi LTM sembra supportare solo connessioni a Sql2005... ma anche limiti su multiconnessione... connessioni diverse allo stesso Sql2005 devono essere comunque gestite da OletX... e questo mi permette di rispondere alla domanda di (luka) con un triste "purtoppo si! :("
Per rispondere ho eseguito alcuni test con il precedente esempio. System.Transactions by design _parte_ usando un LTM e cambia a Oletx automaticamente quando lo ritiene necessario. TransactionManager.DistributedTransactionStarted è l'evento che notifica l'inizio di un transazione distribuita in senso classico. Ecco qui di seguito l'elenco dei test e dei risultati ottenuti aggiungendo una sottoscrizione all'evento appena citato.
- Sql2000 e multiconnessione (nessuna modifca al codice dei vari componenti/classi): l'evento DistributedTransactionStarted scatta durante la prima interazione con il database infatti come da documentazione Sql2000 richiede Oletx.
- Sql2005 e multiconnessione (nessuna modifca al codice dei vari componenti/classi): l'evento DistributedTransactionStarted scatta ancora - inaspettatamente - durante la prima interazione con il database. Questo richiederà un approfondimento ovviamente anche se qualcosa a riguardo lo si può leggere nel paragrafo "Triggering Promotion" di "Introducing System.Transactions in the .NET Framework 2.0"
- Sql2005 e unica connessione (modifca al codice di Table1Manager in modo che DeleteAllItems e InsertABCDEF usino la stessa connessione): l'evento DistributedTransactionStarted scatta SOLO durante l'interazione con message queue, infatti come da documentazione MSMQ richiede Oletx.
System.Transactions è quindi da evitare? Assolutamente non è questo quello che voglio dire! Credo che se occorra implementare un architettura transazionale perchè non considerare System.Transactions un ottima e semplice Facade verso il DTC? Se non sbaglio l'alternativa - escludendo soluzioni custom - sarebbe quella di interfacciarsi _direttamente_ con COM+ implementando ServicedComponent... e se devo/posso dare una mia opinione, mi sento di preferire per semplicità di gestione la nuova soluzione :-p
posted @ domenica 11 dicembre 2005 17:32