Errore TransactionScope nella transazione ambientale non esegue il rollback della transazione

Io uso una transazione ambientale come questa:


using(TransactionScope tran = new TransactionScope()) { CallAMethod1();//INSERT CallAMethod2();//INSERT tran.Complete(); } 

Il metodo CallAMethod2(); restituisce affected rows =-264 Quindi non riesce a inserire tuttavia il primo inserimento è stato eseguito!

Voglio sapere come lavorare con ambient transaction e cosa succede se il secondo metodo ha più di un’azione che necessita di una transazione interna, dovrei inserire queste azioni nella transazione interna? come questo :

  DAL_Helper.Begin_Transaction(); //------Fill newKeysDictioanry affectedRow = DBUtilities.InsertEntityWithTrans("table2", newKeysDictioanry, DAL_Helper); if (affectedRow == 1) { if (!string.IsNullOrEmpty(sp_confirm)) { result_dt = UserTransactionDAL.Run_PostConfirm_SP(sp_PostConfirm, OBJ.ValuesKey, DAL_Helper); if (result_dt.Rows.Count > 0 && result_dt.Rows[0][0].ToString() == "0") { DAL_Helper.current_trans.Commit(); if (DAL_Helper.connectionState == ConnectionState.Open) { DAL_Helper.Close_Connection(); } return 1;// affectedRow; } else { DAL_Helper.current_trans.Rollback(); if (DAL_Helper.connectionState == ConnectionState.Open) { DAL_Helper.Close_Connection(); } return -2; } } //etc 

1) È necessario verificare se tran.Complete(); è chiamato. Se il tran.Complete(); viene chiamato, TransactionScope è considerato completato correttamente.

Da MSDN

Quando l’applicazione completa tutto il lavoro che desidera eseguire in una transazione, è necessario chiamare il metodo Complete una sola volta per informare il gestore transazioni che è accettabile il commit della transazione. Non riuscendo a chiamare questo metodo, la transazione viene interrotta.

La chiamata a tran.Complete(); è quello di informare il gestore delle transazioni per completare la transazione. In realtà, Transaction Manager non tiene traccia dell’adattatore Db e non sa se un’operazione in una connessione ha avuto esito positivo o non è riuscita. L’applicazione deve comunicarlo chiamando Completato

In che modo TransactionScope esegue il rollback delle transazioni?

Per fallire la tua transazione, assicurati di non chiamare tran.Complete(); nel tuo codice:

Se non si verifica alcuna eccezione all’interno dell’ambito della transazione (ovvero, tra l’inizializzazione dell’object TransactionScope e la chiamata del relativo metodo Dispose), la transazione a cui partecipa lo scope è autorizzata a procedere. Se si verifica un’eccezione nell’ambito della transazione, verrà eseguito il rollback della transazione a cui partecipa.

Nel tuo caso, forse puoi lanciare un’eccezione nel tuo CallAMethod2(); se pensi che l’operazione non abbia funzionato, quindi tran.Complete(); non viene chiamato e la transazione viene annullata.

2) La seconda cosa che puoi verificare è se la tua connessione è inclusa nella transazione. TransactionScope non esegue il rollback se la connessione non è inclusa. I possibili problemi sono:

  • Se la connessione esiste prima di entrare nello scope della transazione, non si arruolerà: TransactionScope funziona con connessioni preesistenti?
  • La tua connessione sottostante non supporta l’inserimento automatico.

In questi casi, puoi provare ad inserire manualmente la tua connessione (estratta dal link sopra):

 connection.EnlistTransaction(Transaction.Current) 

Per quanto riguarda la tua seconda domanda:

e se il secondo metodo ha più di un’azione che necessita di una transazione interna, dovrei inserire queste azioni nella transazione interna?

Direi che dipende davvero se consideri il tuo CallAMethod2(); come operazione automatica , il che significa che puoi chiamarlo direttamente altrove senza doverlo racchiudere in una transazione. Nella maggior parte dei casi, avrebbe senso creare transazioni interne in quanto le transazioni possono essere annidate. Nel tuo caso, si consiglia di utilizzare TransactionScope anche in CallAMethod2(); , abbiamo alcune opzioni quando creiamo un nuovo ambito di transazione:

La class TransactionScope fornisce diversi costruttori sovraccaricati che accettano un’enumerazione del tipo TransactionScopeOption, che definisce il comportamento transazionale dell’ambito. Un object TransactionScope ha tre opzioni:

Unisciti alla transazione ambientale o creane una nuova se non ne esiste una.

Essere un nuovo ambito di root, ovvero avviare una nuova transazione e fare in modo che quella transazione sia la nuova transazione ambientale all’interno del proprio ambito.

Non prendere parte a nessuna transazione. Non vi è alcuna transazione ambientale come risultato.

Quale scegliere dipende davvero dalla tua applicazione. Nel tuo caso, immagino tu possa andare con la prima opzione. Di seguito è riportato un esempio da MSDN

 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(); } } 

È ansible utilizzare l’ambito interno ed esterno per la transazione:

 string connectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString; var option = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(60) }; using (var scopeOuter = new TransactionScope(TransactionScopeOption.Required, option)) { using (var conn = new SqlConnection(connectionString)) { using (SqlCommand cmd = conn.CreateCommand()) { cmd.CommandText="INSERT INTO Data(Code, FirstName)VALUES('A-100','Mr.A')"; cmd.Connection.Open(); cmd.ExecuteNonQuery(); } } using (var scopeInner = new TransactionScope(TransactionScopeOption.Required, option)) { using (var conn = new SqlConnection(connectionString)) { using (SqlCommand cmd = conn.CreateCommand()) { cmd.CommandText="INSERT INTO Data(Code, FirstName) VALUES('B-100','Mr.B')"; cmd.Connection.Open(); cmd.ExecuteNonQuery(); } } scopeInner.Complete(); } scopeOuter.Complete(); } 

Leggi cosa dice Khanh TO. Se la connessione viene aperta al di fuori dell’ambito della transazione esterna, la connessione non verrà inclusa.

Questo è il motivo per cui la prima chiamata non ha annullato il rollback quando il secondo ha fallito. Dovrai inserire la tua connessione:

 using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required)) { connection.EnlistTransaction(Transaction.Current); CallAMethod1();//INSERT CallAMethod2();//INSERT tran.Complete(); }