2010-07-06 21 views
15

Ho un oggetto padre (parte di un DAL) che contiene, tra le altre cose, una raccolta (List<t>) di oggetti figlio.TransactionScope: evitare transazioni distribuite

Quando sto salvando l'oggetto nel DB, inserisco/aggiorno il genitore, quindi eseguo il ciclo di ciascun bambino. Per la manutenibilità, ho messo tutto il codice per il bambino in un metodo privato separato.

Stavo per utilizzare le transazioni ADO standard, ma durante i miei viaggi mi sono imbattuto nell'oggetto TransactionScope, che credo mi consenta di avvolgere tutta l'interazione DB nel metodo padre (insieme all'interazione nel metodo figlio) in una transazione.

Fin qui tutto bene ...?

Quindi la prossima domanda è come creare e utilizzare le connessioni all'interno di questo TransactionScope. Ho sentito dire che l'utilizzo di più connessioni, anche se si trovano nello stesso DB, può costringere TransactionScope a pensare che si tratti di una transazione distribuita (che implica un costoso lavoro di DTC).

È il caso? O è, come sembra che stia leggendo altrove, un caso che usa la stessa stringa di connessione (che si presterà al pooling di connessioni) andrà bene?

Più in pratica, posso ...

  1. creare connessioni separate nel bambino genitore & (anche se con la stessa stringa di connessione)
  2. creare una connessione nel genitore una farla passare attraverso un parametro (mi sembra goffo)
  3. Fai qualcos'altro ...?

UPDATE:

Mentre sembra sarei OK usando la mia solita .NET3.5 + e SQL Server 2008+, un'altra parte di questo progetto verrà utilizzato Oracle (10g), così ho potrebbe anche praticare una tecnica che può essere utilizzata in modo coerente tra i progetti.

Quindi passerò semplicemente la connessione ai metodi figlio.


Opzione 1 codice di esempio:

using (TransactionScope ts = new TransactionScope()) 
      { 
       using (SqlConnection conn = new SqlConnection(connString)) 
       { 
        using (SqlCommand cmd = new SqlCommand()) 
        { 
         cmd.Connection = conn; 
         cmd.Connection.Open(); 
         cmd.CommandType = CommandType.StoredProcedure; 

         try 
         { 
          //create & add parameters to command 

          //save parent object to DB 
          cmd.ExecuteNonQuery(); 

          if ((int)cmd.Parameters["@Result"].Value != 0) 
          { 
           //not ok 
           //rollback transaction 
           ts.Dispose(); 
           return false; 
          } 
          else //enquiry saved OK 
          { 
           if (update) 
           { 
            enquiryID = (int)cmd.Parameters["@EnquiryID"].Value; 
           } 

           //Save Vehicles (child objects) 
           if (SaveVehiclesToEPE()) 
           { 
            ts.Complete(); 
            return true; 
           } 
           else 
           { 
            ts.Dispose(); 
            return false; 
           } 
          } 
         } 
         catch (Exception ex) 
         { 
          //log error 
          ts.Dispose(); 
          throw; 
         } 
        } 
       } 
      } 
+3

Vedere [TransactionScope passa automaticamente a MSDTC su alcune macchine?] (Http://stackoverflow.com/questions/1690892/transactionscope-automatically-escalating-to-msdtc-on-some-machines/1693795#1693795). Ci sono molte buone risposte, ma quella che ho collegato è la più sintetica (e pertinente alla tua domanda). Il risultato è che, se utilizzi .NET 2.0 e SQL Server 2005, aumenterai anche utilizzando due connessioni con la stessa stringa di connessione. Questo non è un problema con .NET 3.5 e SQL Server 2008. –

+0

I * am * utilizza .NET 3.5/4 e SQL 2008 in generale, ma occasionalmente potrei usare SQL2005/2000, quindi vale la pena ricordare comunque. Grazie a – CJM

+0

qualcuno può darmi qualche informazione su cosa è una transazione distribuita. spiegare con l'esempio. – Thomas

risposta

24

Molti provider ADO di database (come Oracle ODP.NET) avviano effettivamente transazioni distribuite quando si utilizza TransactionScope per eseguire transazioni su più connessioni, anche quando condividono la stessa stringa di connessione.

Alcuni provider (come SQL2008 in .NET 3.5+) riconoscono quando viene creata una nuova connessione in un ambito di transazione che fa riferimento alla stessa stringa di connessione e non determinerà il funzionamento di DTC. Ma qualsiasi variazione nella stringa di connessione (come i parametri di ottimizzazione) potrebbe impedire che ciò si verifichi, e il comportamento tornerà ad utilizzare una transazione distribuita.

Sfortunatamente, l'unico mezzo affidabile per garantire che le transazioni lavorino insieme senza creare una transazione distribuita è quello di passare l'oggetto di connessione (o lo IDbTransaction) ai metodi che devono "continuare" sulla stessa transazione.

A volte aiuta ad elevare la connessione a un membro della classe in cui si sta svolgendo il lavoro, ma questo può creare situazioni imbarazzanti e complica il controllo della durata e dello smaltimento dell'oggetto di connessione (poiché generalmente preclude l'utilizzo della dichiarazione using).

+0

Ho avuto l'impressione che la connessione debba essere creata all'interno di TransactionScope per essere coperta. Immagino che il semplice passaggio della Connessione sia più semplice e più ordinato di un altro trucco? – CJM

+0

Sai se questo è cambiato con Oracle 12c? Lo vedo contrassegnato come "Disponibile in ODAC 12c o successivo". qui: https://apex.oracle.com/pls/apex/f?p = 18357: 39: 1473540763666 :: NO :: P39_ID: 27121 – pauloya

1

Nel tuo esempio il TransactionScope è ancora nel contesto di un metodo, si potrebbe semplicemente creare uno SqlTransaction con più comandi sotto quello. Utilizzare TransactionScope se si desidera spostare la transazione da un metodo, ad esempio, il chiamante di tale metodo o se si accede a più database.

Aggiornamento: non importa, ho appena individuato la chiamata figlio. In questa situazione, è possibile passare l'oggetto connessione a classi figlio. Inoltre, non è necessario disporre manualmente di TransactionScope - l'utilizzo di blocchi si comporta come i blocchi try-finally e consente di eseguire lo smaltimento anche in caso di eccezioni.

Aggiornamento 2: meglio ancora, passare IDbTransaction alla classe figlio. La connessione può essere recuperata da quello.

+0

Sì, avevo capito che lo smaltimento non era esplicitamente necessario, ma quando ho adattato TransactionScope al mio codice, chiaramente mi ero dimenticato e ho continuato a convertire le mie vecchie istruzioni trm.Rollback in ts.Dispose senza pensare. Buon posto! – CJM

+0

Re ** Update 2 ** -È sufficiente passare 'IDbConnection' alla classe figlio. Se usi 'IbConnection.BeginTransaction()', tutti i comandi creati con 'IDbConnection.CreateCommand()' avranno automaticamente la transazione associata. Questo è meglio perché è un parametro in meno per passare la tua gerarchia, e riduce l'accoppiamento in quanto le classi figlie non dovranno preoccuparsi se stanno eseguendo o meno all'interno di una transazione. –

2

Empiricamente, ho stabilito che (per il provider di SQL Server) se il processo può usufruire di pool di connessioni per condividere la connessione (e l'operazione) tra i processi padre e figlio, il DTC non necessariamente diventerà coinvolti.

Questo è un grande "se", però, secondo il vostro esempio, il collegamento creato dal processo genitore non può essere condiviso dai processi figli (non si chiude/rilasciare la connessione prima di richiamare i processi figli). Ciò comporterà una transazione che si estende su due connessioni effettive, il che comporterà la promozione della transazione a una transazione distribuita.

Sembra che sarebbe facile rifattorizzare il codice per evitare questo scenario: basta chiudere la connessione creata dal processo padre prima di richiamare i processi figli.

+0

Stai dicendo che se passo l'oggetto di connessione, devo chiudere il genitore e riaprire il bambino? – CJM

+0

No. Stavo scontando che stavate cercando di sfruttare la praticità di TransactionScope senza che la transazione venisse promossa su una transazione distribuita. Sembra che questo non sia possibile quando si mira ai database Oracle, ma è possibile scegliere come target i database di SQL Server ... a condizione che le connessioni possano essere raggruppate e che ci sia solo una connessione aperta alla volta. –

Problemi correlati