Non tutto oro quello che è System.Transactions

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.

  1. 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.
  2. 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"
  3. 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

Print
Comments have been closed on this topic.
«agosto»
domlunmarmergiovensab
31123456
78910111213
14151617181920
21222324252627
28293031123
45678910