2013-05-31 16 views
9

Quando abbiamo bisogno di fare l'accesso al database nella nostra applicazione, usiamo i seguenti modelli:SqlConnection ed evitando la promozione in MSDTC

  • Per l'esecuzione di query, abbiamo una classe factory statico con un metodo CreateOpenConnection, che non fa altro che new SqlConnection(myConnectionString) e chiama Open() su di esso. Questo metodo viene chiamato prima di eseguire una query e la connessione viene eliminata dopo il ritorno della query.
  • Per inserti/aggiornamenti/elimina usiamo un'unità di modello di lavoro dove i cambiamenti vengono dosati e sottoposte al database con una chiamata a work.Commit() in questo modo:

work.Commit:

using (var tranScope = new TransactionScope(TransactionScopeOption.RequiresNew)) 
{ 
    using (var conn = DapperFactory.CreateOpenConnection()) 
    { 
     var count = _changeTracker.CommitChanges(conn); 

     tranScope.Complete(); 

     return count; 
    } 
} 

Questo sembra funzionare perfettamente per l'utilizzo generale come parte di un servizio web, ma attualmente mi sta dando problemi MSDTC quando provo a utilizzarlo in combinazione con Rebus.

Da quello che posso dire, Rebus (quando gestisce un messaggio in coda) crea un nuovo TransactionScope in modo che in caso di un errore nel gestire il messaggio, è possibile eseguire il rollback. Ora, questo di per sé ha funzionato bene finora. Posso aprire un nuovo SqlConnection all'interno di un gestore di messaggi di Rebus senza problemi (tuttavia, utilizzando le query legacy Entity Framework e manuale SqlConnections all'interno dello stesso Rebus TransactionScope non funziona, ma non considero un problema al momento) . Ma ieri ho chiesto alla seguente domanda:

Serial processing of a certain message type in Rebus

Al che la risposta sembra essere quello di utilizzare la funzione saga di Rebus. Ho provato a implementarlo e configurato in modo tale che la saga di Rebus venga mantenuta su un nuovo database SQL Server (con una stringa di connessione distinta). Presumibilmente, usando che la persistenza di SQL Server apre una SqlConnection propria, perché ogni volta che cerco di creare un SqlConnection ora, ottengo la seguente eccezione:

Accesso alla rete per Distributed Transaction Manager (MSDTC) è stato disabilitato. Abilitare DTC per l'accesso alla rete nella configurazione di sicurezza per MSDTC utilizzando lo strumento di amministrazione dei servizi componenti.

Abilitazione MSDTC è qualcosa che vorrei molto, molto piace molto per evitare di fare, per quanto riguarda la configurazione e sovraccarico delle prestazioni. E potrei sbagliarmi, ma anche non sembra necessario.

Quello che presumo sta succedendo qui è che Rebus crea un ambiente TransactionScope e che lo SqlConnection crea arresti a tale scopo. E quando provo a creare il mio SqlConnection, tenta anche di arruolarmi in tale ambito ambientale e poiché sono coinvolte più connessioni, esso viene promosso a MSDTC, che fallisce.

Ho un'idea su come risolvere questo problema, ma non so se è la cosa giusta da fare. Quello che vorrei fare è:

  • Aggiungere Enlist=false alla stringa di connessione della mia applicazione in modo che non si arresti mai alle transazioni ambientali.
  • Modificare il metodo Commit in modo che non crei un nuovo TransactionScope (che la mia connessione non sottoscriverà più perché ho appena detto che non dovrebbe) ma che utilizza conn.BeginTransaction.

Come così:

var transaction = conn.BeginTransaction(); 

try 
{ 
    var count = _changeTracker.CommitChanges(conn); 
    transaction.Commit(); 
    return count; 
} 
catch 
{ 
    transaction.Rollback(); 
    throw; 
} 
finally 
{ 
    transaction.Dispose(); 
} 

io non sono solo sicuro se questo è l'approccio giusto e ciò che i possibili svantaggi sono.

Qualche consiglio?

AGGIORNAMENTO: Per chiarire, non è la work.Commit() che mi sta dando problemi, sono abbastanza sicuro che avrebbe funzionato, ma non ho mai arrivare lì perché il mio l'interrogazione è quello che non riesce.

Un esempio di ciò che non riesce:

public int? GetWarehouseID(int appID) 
{ 
    var query = @" 
select top 1 ID from OrganizationUnits o 
where TypeID & 16 = 16 /* warehouse */"; 

    using (var conn = _dapper.OpenConnection()) 
    { 
    var id = conn.Query<int?>(query).FirstOrDefault(); 

    return id; 
    } 
} 

Questo viene chiamato quando un TransactionScope è stata creata da Rebus, così come dopo un SqlConnection viene aperto da Rebus. All'apertura miaSqlConnection, tenta di arruolarsi e va in crash

+0

Se si utilizza "Enlist = false", sicuramente ciò renderebbe il vostro TransactionScope inutile? perché la connessione * non ci sarà * –

+0

Allo stesso modo, il tuo codice 'BeginTransaction' * non sta usando la transazione * - una transazione ADO.NET deve essere specificata esplicitamente sul comando, quindi devi passare 'transazione' in' CommitChanges', sicuramente? –

+0

Puoi chiarire esattamente quale versione di SQL Server stai usando? –

risposta

3

Sono un po 'sorpreso che tu stia vedendo questo, perché il RequiresNewdovrebbe significa che è isolato dall'altra transazione; di solito, questo messaggio significa che sono state attivate 2 connessioni all'interno di un ambito di transazione: sei sicuro non c'è nessun altro codice che crea/apre una connessione all'interno di quel blocco?

La soluzione proposta dovrebbe funzionare, anche se in qualche modo lo TransactionScopeOption.Suppress potrebbe essere più conveniente della modifica della configurazione (ma dovrebbe funzionare). Tuttavia, c'è un problema: le transazioni ADO.NET devono essere passati ai singoli comandi, quindi si avrebbe bisogno (anche mettere in ordine il codice un po '):

using(var transaction = conn.BeginTransaction()) { 
    try { 
     var count = _changeTracker.CommitChanges(conn, transaction); 
     transaction.Commit(); 
     return count; 
    } catch { 
     transaction.Rollback(); 
     throw; 
    } 
} 

dove CommitChanges accetta una transazione - magari utilizzando parametri opzionali:

int CommitChanges(DbConnection connection, DbTransaction transaction = null) 
{ ... } 

tuo denominazione di DapperFactory suggerisce che si sta utilizzando "Dapper" - in questo caso, si può semplicemente passare che in "dapper" se è nullo o non è, vale a dire

conn.Execute(sql, args, transaction: transaction); 
+0

Spiacente, per chiarire (e vedere il mio aggiornamento) non è il 'commit' che fallisce, sono le domande. Il mio codice per il commit era solo per mostrare come propongo di modificare che se aggiungo 'Enlist = false' e ​​continuo a mantenere il comando 'Commit' come atomico (perché in effetti non farebbe più nulla con il' TransactionScope' che ho creato E la parte di passare la transazione è qualcosa che ho trascurato, grazie per il promemoria :) – JulianR

+0

@ JulianR non pensavo che il 'Commit' * era * errore; Stavo cercando di illustrare 2 punti: in primo luogo, che il codice necessario per passare la transazione - che poi mi dimentico di includere nell'esempio *** (d'oh! Per favore vedi modifica); e * secondariamente * che il tuo 'try' /' catch'/'finally' era troppo complicato e può essere semplificato –

+0

Sì, mi riferivo al tuo commento che ti aspettavi che' RequiresNew' funzionasse, cosa che probabilmente fa. Ma supponendo che io esegua il necessario cambio di passaggio in 'DbConnection', pensi che il mio nuovo approccio funzionerebbe? Quello di cui sono preoccupato è che gli inserti/aggiornamenti/eliminazioni non siano atomici o 'enlist = false' che hanno conseguenze negative/strane per l'interrogazione. In un certo senso, sembra di "fissare" un'eccezione inghiottendola, se capisci cosa intendo :) – JulianR

1

Questo dipende in gran parte la versione di SQL Server che si sta utilizzando. Vedere here per un'altra domanda SO che affronta un problema simile.

Ha a che fare con il modo in cui SQL 2005 e SQL 2008 differiscono nella gestione di più connessioni all'interno dello stesso TransactionScope. Ad esempio, SQL 2008 può aprire più connessioni nello stesso TransactionScope senza inoltrarsi a MSDTC.

Potrebbe essere questo il problema che state vedendo

Se questo è il caso, credo che le uniche due opzioni sono l'aggiornamento a SQL 2008 o consentono MSDTC. Capisco che entrambe queste opzioni sono probabilmente enormi mal di testa.

+1

È necessario sottolineare che la gestione 2008 si applica ancora solo quando la stringa di connessione e l'identità del thread delle due connessioni sono identiche e quando le connessioni sono aperte in serie (non contemporaneamente) –