In unserem Projekt verwenden wir TransactionScope, um sicherzustellen, dass unsere Datenzugriffsschicht ihre Aktionen in einer Transaktion ausführt. Wir möchten nicht, dass der MSDTC-Dienst auf den Computern unserer Endbenutzer aktiviert wird.
Das Problem ist, dass wir auf der Hälfte unserer Entwicklercomputer mit deaktiviertem MSDTC arbeiten können. Die andere Hälfte muss aktiviert sein, sonst wird die Fehlermeldung "MSDTC auf [SERVER] ist nicht verfügbar" angezeigt .
Ich habe mich wirklich am Kopf gekratzt und ernsthaft darüber nachgedacht, auf eine hausgemachte TransactionScope-ähnliche Lösung zurückzugreifen, die auf ADO.NET-Transaktionsobjekten basiert. Es ist scheinbar verrückt - der gleiche Code , das funktioniert (und nicht eskaliert) auf die Hälfte unseres Entwicklers tut eskalieren auf der anderen Entwickler.
Ich hatte auf eine bessere Antwort auf Trace gehofft, warum eine Transaktion zu DTC eskaliert, aber leider nicht.
Hier ist ein Beispielcode, der die Probleme verursacht. Auf den Computern, die versuchen zu eskalieren, versucht er, bei der zweiten Verbindung zu eskalieren. Open () (und ja, es ist derzeit keine andere Verbindung offen.)
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();
}
Wir haben uns wirklich eingegraben und versucht, das herauszufinden. Hier sind einige Informationen zu den Maschinen, auf denen es funktioniert:
- Entwicklung 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Entwickler, bei denen es nicht funktioniert:
- Dev 4: Windows 7 x64,
SQL2008SQL2005 - Dev 5: Windows Vista x86, SQL2005
- Dev 6: Windows XP X86, SQL2005
- Mein Heim-PC: Windows Vista Home Premium, x86, SQL2005
Ich sollte hinzufügen, dass alle Computer, um das Problem zu beheben, vollständig mit allem gepatcht wurden, was in Microsoft Update verfügbar ist.
Update 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ beschreibt ein ähnliches Problem ... bereits 2006!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - Lesen Sie dieses Codebeispiel, es zeigt deutlich eine verschachtelte zweite Verbindung (zu einem zweiten SQL Server, tatsächlich), die zu DTC eskalieren wird. Wir tun dies nicht in unserem Code - wir verwenden weder unterschiedliche SQL Server noch unterschiedliche Verbindungszeichenfolgen, noch haben wir verschachtelte sekundäre Verbindungen geöffnet - es sollte keine Eskalation zum DTC geben .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (ab 2005) erläutert, wie bei einer Verbindung mit SQL2000 immer eine Eskalation zu DTC auftritt. Wir verwenden SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN bei Transaktionseskalation.
Diese MSDN-Transaktionseskalationsseite gibt an, dass die folgenden Bedingungen dazu führen, dass eine Transaktion zu DTC eskaliert:
- Mindestens eine dauerhafte Ressource, die keine einphasigen Benachrichtigungen unterstützt, wird in die Transaktion aufgenommen.
- In die Transaktion werden mindestens zwei dauerhafte Ressourcen aufgenommen, die einphasige Benachrichtigungen unterstützen. Wenn Sie beispielsweise eine einzelne Verbindung mit registrieren, wird keine Transaktion hochgestuft. Wenn Sie jedoch eine zweite Verbindung zu einer Datenbank öffnen, wodurch die Datenbank registriert wird, erkennt die System.Transactions-Infrastruktur, dass es sich um die zweite dauerhafte Ressource in der Transaktion handelt, und eskaliert sie zu einer MSDTC-Transaktion.
- Eine Anforderung zum "Marshalling" der Transaktion in eine andere Anwendungsdomäne oder einen anderen Prozess wird aufgerufen. Zum Beispiel die Serialisierung des Transaktionsobjekts über eine Anwendungsdomänengrenze hinweg. Das Transaktionsobjekt wird nach Wert gemarshallt. Dies bedeutet, dass jeder Versuch, es über eine Anwendungsdomänengrenze (auch im selben Prozess) zu übergeben, zur Serialisierung des Transaktionsobjekts führt. Sie können die Transaktionsobjekte übergeben, indem Sie eine Remote-Methode aufrufen, die eine Transaktion als Parameter verwendet, oder Sie können versuchen, auf eine Remote-Transaktionskomponente zuzugreifen. Dies serialisiert das Transaktionsobjekt und führt zu einer Eskalation, wie wenn eine Transaktion über eine Anwendungsdomäne serialisiert wird. Es wird verteilt und der lokale Transaktionsmanager ist nicht mehr ausreichend.
Wir erleben nicht # 3. # 2 findet nicht statt, da immer nur eine Verbindung vorhanden ist und es sich auch um eine einzelne "dauerhafte Ressource" handelt. Gibt es eine Möglichkeit, dass # 1 passieren könnte? Eine SQL2005 / 8-Konfiguration, die dazu führt, dass einphasige Benachrichtigungen nicht unterstützt werden?
Update 2:
Persönlich wurden alle SQL Server-Versionen erneut untersucht - "Dev 3" hat tatsächlich SQL2008, und "Dev 4" ist tatsächlich SQL2005. Das wird mich lehren, meinen Mitarbeitern nie wieder zu vertrauen. ;) Aufgrund dieser Datenänderung bin ich mir ziemlich sicher, dass wir unser Problem gefunden haben. Unsere SQL2008-Entwickler hatten das Problem nicht, da SQL2008 eine Menge großartiger Funktionen enthält, die SQL2005 nicht hat.
Da wir SQL2005 unterstützen, können wir TransactionScope nicht mehr so verwenden wie bisher. Wenn wir TransactionScope verwenden möchten, müssen wir ein einzelnes SqlConnection-Objekt übergeben ... Dies scheint in Situationen problematisch zu sein, in denen die SqlConnection nicht einfach weitergegeben werden kann. Es riecht nur nach einer globalen SqlConnection-Instanz. Bank!
Update 3
Nur um hier in der Frage zu klären:
SQL2008:
- Ermöglicht mehrere Verbindungen innerhalb eines einzelnen TransactionScope (wie im obigen Beispielcode gezeigt).
- Vorsichtsmaßnahme Nr. 1: Wenn diese mehreren SqlConnections verschachtelt sind, dh zwei oder mehr SqlConnections gleichzeitig geöffnet werden, wird TransactionScope sofort zu DTC eskaliert.
- Vorsichtsmaßnahme Nr. 2: Wenn eine zusätzliche SqlConnection für eine andere 'dauerhafte Ressource' (dh einen anderen SQL Server) geöffnet wird, wird sie sofort zu DTC eskaliert
SQL2005:
- Ermöglicht nicht mehrere Verbindungen innerhalb eines einzelnen TransactionScope-Zeitraums. Es eskaliert, wenn eine zweite SqlConnection geöffnet wird.
Update 4
Im Interesse dieser Frage machen noch ein Durcheinander nützlich, und nur aus Gründen mehr Klarheit, hier, wie Sie SQL2005 mit einem zu DTC eskalieren bekommen können einzelne SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Dies scheint mir nur kaputt zu sein, aber ich denke, ich kann verstehen, ob jeder Anruf SqlConnection.Open()
aus dem Verbindungspool stammt.
"Warum könnte das aber passieren?" Wenn Sie einen SqlTableAdapter für diese Verbindung verwenden, bevor sie geöffnet wird, öffnet und schließt der SqlTableAdapter die Verbindung und beendet die Transaktion effektiv für Sie, da Sie sie jetzt nicht erneut öffnen können.
Um TransactionScope mit SQL2005 erfolgreich verwenden zu können, muss im Grunde ein globales Verbindungsobjekt vorhanden sein, das ab dem Zeitpunkt der ersten Instanziierung von TransactionScope geöffnet bleibt, bis es nicht mehr benötigt wird. Neben dem Code-Geruch eines globalen Verbindungsobjekts widerspricht das Öffnen und Schließen der Verbindung zuerst der Logik, eine Verbindung so spät wie möglich zu öffnen und so schnell wie möglich zu schließen.