Dans notre projet, nous utilisons TransactionScope pour garantir que notre couche d'accès aux données exécute ses actions dans une transaction. Nous visons à ne pas exiger que le service MSDTC soit activé sur les machines de nos utilisateurs finaux.
Le problème est que, sur la moitié de nos machines de développement, nous pouvons exécuter MSDTC désactivé. L'autre moitié doit l'avoir activé ou ils obtiennent le message d'erreur «MSDTC sur [SERVEUR] n'est pas disponible» .
Cela m'a vraiment fait me gratter la tête et m'a sérieusement envisagé de revenir à une solution de type TransactionScope personnalisée basée sur des objets de transaction ADO.NET. Il est apparemment fou - le même code que les travaux (et ne dégénèrent pas) sur la moitié de notre développeur est fait AMPLIFIENT l'autre développeur de.
J'espérais une meilleure réponse à Trace pourquoi une transaction est escaladée en DTC mais malheureusement ce n'est pas le cas.
Voici un exemple de code qui causera le problème, sur les machines qui tentent de s'intensifier, il essaie de s'intensifier sur la deuxième connexion.Open () (et oui, aucune autre connexion n'est ouverte à ce moment-là.)
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();
}
Nous avons vraiment creusé et essayé de comprendre cela. Voici quelques informations sur les machines sur lesquelles il fonctionne:
- Dev 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Développeurs sur lesquels il ne fonctionne pas:
- Dev 4: Windows 7 x64,
SQL2008SQL2005 - Dev 5: Windows Vista x86, SQL2005
- Dev 6: Windows XP X86, SQL2005
- Mon PC personnel: Windows Vista Home Premium, x86, SQL2005
Je dois ajouter que toutes les machines, afin de traquer le problème, ont été entièrement corrigées avec tout ce qui est disponible à partir de Microsoft Update.
Mise à jour 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ décrit un problème similaire ... en 2006!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - lisez cet exemple de code, il montre clairement une seconde connexion imbriquée (à un deuxième serveur SQL, en fait) qui dégénérera en DTC. Nous ne faisons pas cela dans notre code - nous n'utilisons pas différents serveurs SQL, ni différentes chaînes de connexion, ni aucune ouverture de connexions secondaires imbriquées - il ne devrait pas y avoir d'escalade vers DTC .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (à partir de 2005) explique comment l'escalade vers DTC se produira toujours lors de la connexion à SQL2000. Nous utilisons SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN sur l'escalade des transactions.
Cette page d'escalade de transactions MSDN indique que les conditions suivantes entraîneront l'escalade d'une transaction en DTC:
- Au moins une ressource durable qui ne prend pas en charge les notifications monophasées est inscrite dans la transaction.
- Au moins deux ressources durables qui prennent en charge les notifications en une seule phase sont inscrites dans la transaction. Par exemple, l'inscription d'une connexion unique avec n'entraîne pas la promotion d'une transaction. Cependant, chaque fois que vous ouvrez une deuxième connexion à une base de données provoquant l'enrôlement de la base de données, l'infrastructure System.Transactions détecte qu'il s'agit de la deuxième ressource durable dans la transaction et l'escalade en une transaction MSDTC.
- Une demande de "marshaler" la transaction vers un domaine d'application différent ou un processus différent est invoquée. Par exemple, la sérialisation de l'objet de transaction à travers une limite de domaine d'application. L'objet de transaction est marshalé par valeur, ce qui signifie que toute tentative de le traverser une frontière de domaine d'application (même dans le même processus) entraîne la sérialisation de l'objet de transaction. Vous pouvez transmettre les objets de transaction en effectuant un appel sur une méthode distante qui prend une transaction comme paramètre ou vous pouvez essayer d'accéder à un composant desservi par transaction à distance. Cela sérialise l'objet de transaction et entraîne une escalade, comme lorsqu'une transaction est sérialisée sur un domaine d'application. Il est en cours de distribution et le gestionnaire de transactions local n'est plus adéquat.
Nous ne connaissons pas le n ° 3. # 2 ne se produit pas car il n'y a qu'une seule connexion à la fois, et c'est aussi vers une seule «ressource durable». Y a-t-il un moyen que le n ° 1 puisse se produire? Une configuration SQL2005 / 8 qui l'empêche de prendre en charge les notifications monophasées?
Mise à jour 2:
A réexaminé, personnellement, les versions de SQL Server de tout le monde - "Dev 3" a en fait SQL2008 et "Dev 4" est en fait SQL2005. Cela m'apprendra à ne plus jamais faire confiance à mes collègues. ;) En raison de ce changement de données, je suis presque sûr que nous avons trouvé notre problème. Nos développeurs SQL2008 ne rencontraient pas le problème parce que SQL2008 a de nombreuses quantités impressionnantes incluses que SQL2005 n'a pas.
Cela me dit également que parce que nous allons prendre en charge SQL2005, nous ne pouvons pas utiliser TransactionScope comme nous l'avons été, et si nous voulons utiliser TransactionScope, nous allons devoir passer un seul objet SqlConnection autour ... ce qui semble problématique dans les situations où la SqlConnection ne peut pas être facilement contournée ... elle sent juste l'instance de global-SqlConnection. Banc!
Mise à jour 3
Juste pour clarifier ici dans la question:
SQL2008:
- Autorise plusieurs connexions au sein d'un même TransactionScope (comme illustré dans l'exemple de code ci-dessus.)
- Avertissement 1: si ces multiples SqlConnections sont imbriquées, c'est-à-dire que deux ou plusieurs SqlConnections sont ouvertes en même temps, TransactionScope passera immédiatement au DTC.
- Mise en garde n ° 2: si une SqlConnection supplémentaire est ouverte vers une autre «ressource durable» (c'est-à-dire: un autre serveur SQL), elle passera immédiatement à DTC
SQL2005:
- N'autorise pas plusieurs connexions dans un même TransactionScope, point. Il s'aggravera quand / si une seconde SqlConnection est ouverte.
Mise à jour 4
Dans l'intérêt de rendre cette question encore plus compliquée , et pour plus de clarté, voici comment vous pouvez faire passer SQL2005 en DTC avec un seul SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Cela me semble tout simplement cassé, mais je suppose que je peux comprendre si chaque appel à SqlConnection.Open()
est en train de s'emparer du pool de connexions.
"Mais pourquoi cela pourrait-il arriver?" Eh bien, si vous utilisez un SqlTableAdapter sur cette connexion avant son ouverture, le SqlTableAdapter ouvrira et fermera la connexion, terminant ainsi la transaction pour vous car vous ne pouvez plus la rouvrir.
Donc, pour utiliser TransactionScope avec SQL2005, vous devez disposer d'une sorte d'objet de connexion globale qui reste ouvert à partir du moment où le premier TransactionScope est instancié jusqu'à ce qu'il ne soit plus nécessaire. Outre l'odeur de code d'un objet de connexion globale, ouvrir la connexion en premier et la fermer en dernier est contraire à la logique d'ouvrir une connexion le plus tard possible et de la fermer le plus tôt possible.