Nel nostro progetto stiamo utilizzando TransactionScope per garantire che il nostro livello di accesso ai dati esegua le sue azioni in una transazione. Puntiamo a non richiedere l'abilitazione del servizio MSDTC sui computer dei nostri utenti finali.
Il problema è che su metà delle macchine dei nostri sviluppatori, possiamo eseguire MSDTC disabilitato. L'altra metà deve averlo abilitato o ottengono il messaggio di errore "MSDTC su [SERVER] non disponibile" .
Mi ha davvero fatto grattare la testa e mi sta seriamente prendendo in considerazione l'idea di tornare a una soluzione simile a TransactionScope fatta in casa basata su oggetti di transazione ADO.NET. E 'apparentemente folle - lo stesso codice che funziona (e non si intensificano) sulla metà del nostro sviluppatore fa escalation d'altro sviluppatore.
Speravo in una risposta migliore a Trace perché una transazione è stata inoltrata a DTC, ma sfortunatamente non lo è.
Ecco un po 'di codice di esempio che causerà il problema, sui computer che tentano di eseguire l'escalation, tenta di eseguire l'escalation sulla seconda connessione.Open () (e sì, al momento non esiste un'altra connessione aperta).
using (TransactionScope transactionScope = new TransactionScope() {
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open();
using (SqlDataReader reader = command.ExecuteReader()) {
// use the reader
connection.Close();
}
}
}
// Do other stuff here that may or may not involve enlisting
// in the ambient transaction
using (SqlConnection connection = new SqlConnection(_ConStr)) {
using (SqlCommand command = connection.CreateCommand()) {
// prep the command
connection.Open(); // Throws "MSDTC on [SERVER] is unavailable" on some...
// gets here on only half of the developer machines.
}
connection.Close();
}
transactionScope.Complete();
}
Abbiamo davvero scavato e cercato di capirlo. Ecco alcune informazioni sulle macchine su cui funziona:
- Dev 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Sviluppatori su cui non funziona:
- Dev 4: Windows 7 x64,
SQL2008SQL2005 - Dev 5: Windows Vista x86, SQL2005
- Dev 6: Windows XP X86, SQL2005
- My Home PC: Windows Vista Home Premium, x86, SQL2005
Dovrei aggiungere che tutte le macchine, nel tentativo di dare la caccia al problema, sono state completamente patchate con tutto ciò che è disponibile da Microsoft Update.
Aggiornamento 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ descrive un problema simile ... nel 2006!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx : leggi questo esempio di codice, mostra chiaramente una connessione al secondo nidificato (a un secondo server SQL, in realtà) che passerà a DTC. Non lo stiamo facendo nel nostro codice - non stiamo utilizzando server SQL diversi, né stringhe di connessione diverse, né l'apertura di connessioni secondarie nidificate - non dovrebbe esserci l'escalation a DTC .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (dal 2005) parla di come l'escalation a DTC avverrà sempre quando ci si connette a SQL2000. Stiamo usando SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN sull'escalation delle transazioni.
La pagina di escalation delle transazioni MSDN indica che le seguenti condizioni causeranno l'escalation di una transazione a DTC:
- Almeno una risorsa durevole che non supporta le notifiche monofase è inclusa nella transazione.
- Nella transazione sono incluse almeno due risorse durevoli che supportano le notifiche monofase. Ad esempio, l'inserimento di una singola connessione con non comporta la promozione di una transazione. Tuttavia, ogni volta che si apre una seconda connessione a un database che causa l'arruolamento del database, l'infrastruttura System.Transactions rileva che è la seconda risorsa duratura nella transazione e la converte in una transazione MSDTC.
- Viene invocata una richiesta per "eseguire il marshalling" della transazione in un dominio applicazione diverso o in un processo diverso. Ad esempio, la serializzazione dell'oggetto transazione attraverso un limite del dominio dell'applicazione. L'oggetto transazione è suddiviso per valore, il che significa che qualsiasi tentativo di passarlo attraverso un confine del dominio dell'applicazione (anche nello stesso processo) comporta la serializzazione dell'oggetto transazione. È possibile passare gli oggetti della transazione effettuando una chiamata su un metodo remoto che accetta una Transazione come parametro oppure si può provare ad accedere a un componente remoto gestito dalla transazione. Ciò serializza l'oggetto della transazione e si traduce in un'escalation, come quando una transazione è serializzata in un dominio dell'applicazione. Viene distribuito e il gestore delle transazioni locale non è più adeguato.
Non stiamo riscontrando il n. 3. # 2 non sta accadendo perché esiste sempre una sola connessione alla volta ed è anche una singola "risorsa durevole". C'è un modo in cui potrebbe accadere il n. 1? Qualche configurazione SQL2005 / 8 che non supporta le notifiche monofase?
Aggiornamento 2:
Ho riesaminato, personalmente, le versioni di SQL Server di tutti: "Dev 3" in realtà ha SQL2008 e "Dev 4" è in realtà SQL2005. Questo mi insegnerà a non fidarmi mai più dei miei colleghi. ;) A causa di questo cambiamento nei dati, sono abbastanza sicuro di aver trovato il nostro problema. I nostri sviluppatori SQL2008 non stavano riscontrando il problema perché SQL2008 ha copiose quantità di fantastici inclusi che SQL2005 non ha.
Mi dice anche che, poiché supporteremo SQL2005, non possiamo usare TransactionScope come siamo stati e se vogliamo usare TransactionScope dovremo passare un singolo oggetto SqlConnection in giro ... che sembra problematico in situazioni in cui SqlConnection non può essere facilmente trasmesso ... ha solo l'odore dell'istanza global-SqlConnection. Banco!
Aggiornamento 3
Giusto per chiarire qui nella domanda:
SQL2008:
- Consente connessioni multiple all'interno di un singolo TransactionScope (come dimostrato nel codice di esempio sopra).
- Avvertenza n. 1: se si annidano quelle più connessioni SqlConnessione, ovvero due o più connessioni SqlConness vengono aperte contemporaneamente, TransactionScope passerà immediatamente al DTC.
- Avvertenza n. 2: se un'ulteriore SqlConnection viene aperta a una diversa "risorsa durevole" (ovvero: un diverso SQL Server), passerà immediatamente al DTC
SQL2005:
- Non consente più connessioni all'interno di un singolo TransactionScope, punto. Si intensificherà quando / se viene aperto un secondo SqlConnection.
Aggiornamento 4
Nell'interesse di fare questa domanda ancora più di un pasticcio utile, e solo per amor di più di chiarezza, ecco come si può arrivare SQL2005 a crescere a DTC con un singolo SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Questo mi sembra rotto, ma credo di poter capire se ogni chiamata a SqlConnection.Open()
viene catturata dal pool di connessioni.
"Perché potrebbe succedere, però?" Bene, se usi un SqlTableAdapter contro quella connessione prima che venga aperto, SqlTableAdapter aprirà e chiuderà la connessione, completando in modo efficace la transazione per te perché ora non puoi riaprirla.
Quindi, fondamentalmente, per utilizzare con successo TransactionScope con SQL2005 è necessario disporre di una sorta di oggetto di connessione globale che rimanga aperto dal punto in cui TransactionScope viene istanziato fino a quando non è più necessario. Oltre all'odore di codice di un oggetto connessione globale, aprire prima la connessione e chiuderla per ultima è in contrasto con la logica di aprire una connessione il più tardi possibile e chiuderla il prima possibile.