En nuestro proyecto, estamos utilizando TransactionScope para garantizar que nuestra capa de acceso a datos realice sus acciones en una transacción. Nuestro objetivo es no exigir que el servicio MSDTC esté habilitado en las máquinas de nuestros usuarios finales.
El problema es que, en la mitad de las máquinas de nuestros desarrolladores, podemos ejecutar MSDTC deshabilitado. La otra mitad debe tenerlo habilitado o recibirán el mensaje de error "MSDTC en [SERVIDOR] no está disponible" .
Realmente me tiene rascándome la cabeza y me hace pensar seriamente en volver a una solución similar a TransactionScope basada en el hogar basada en objetos de transacción ADO.NET. Aparentemente es una locura: el mismo código que funciona (y no aumenta) en la mitad de nuestros desarrolladores sí lo hace en los otros desarrolladores.
Esperaba una mejor respuesta a Trace por qué una transacción se escala a DTC, pero desafortunadamente no.
Aquí hay un fragmento de código de muestra que causará el problema, en las máquinas que intentan escalar, intenta escalar en la segunda conexión. Abrir () (y sí, no hay otra conexión abierta en ese momento).
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();
}
Realmente investigamos y tratamos de resolver esto. Aquí hay información sobre las máquinas en las que funciona:
- Dev 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Dev 3: Windows 7 x64
SQL2005SQL2008
Desarrolladores en los que no funciona:
- 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
Debo agregar que todas las máquinas, en un esfuerzo por detectar el problema, han sido completamente parcheadas con todo lo que está disponible en Microsoft Update.
Actualización 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ describe un problema similar ... ¡en 2006!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx : lea esa muestra de código, demuestra claramente una conexión de segundo anidado (a un segundo servidor SQL, en realidad) que se intensificará a DTC. No estamos haciendo esto en nuestro código , no estamos usando diferentes servidores SQL, ni diferentes cadenas de conexión, ni tenemos conexiones secundarias anidadas que se abren, no debería haber escalado a DTC .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (desde 2005) habla sobre cómo siempre ocurrirá la escalada a DTC al conectarse a SQL2000. Estamos usando SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN en la escalada de transacciones.
Esa página de escalado de transacciones de MSDN establece que las siguientes condiciones harán que una transacción se escale a DTC:
- Al menos un recurso duradero que no admite notificaciones monofásicas se incluye en la transacción.
- Al menos dos recursos duraderos que admiten notificaciones monofásicas se inscriben en la transacción. Por ejemplo, alistar una sola conexión no hace que se promocione una transacción. Sin embargo, cada vez que abre una segunda conexión a una base de datos que hace que la base de datos se enliste, la infraestructura de System.Transactions detecta que es el segundo recurso duradero en la transacción y la escala a una transacción MSDTC.
- Se invoca una solicitud para "ordenar" la transacción a un dominio de aplicación diferente o un proceso diferente. Por ejemplo, la serialización del objeto de transacción a través de un límite de dominio de aplicación. El objeto de transacción se ordena por valor, lo que significa que cualquier intento de pasarlo a través de un límite de dominio de aplicación (incluso en el mismo proceso) da como resultado la serialización del objeto de transacción. Puede pasar los objetos de transacción haciendo una llamada en un método remoto que toma una transacción como parámetro o puede intentar acceder a un componente remoto de servicio transaccional. Esto serializa el objeto de transacción y da como resultado una escalada, como cuando una transacción se serializa en un dominio de aplicación. Se está distribuyendo y el administrador de transacciones local ya no es adecuado.
No estamos experimentando el # 3. # 2 no está sucediendo porque solo hay una conexión a la vez, y también es a un solo 'recurso duradero'. ¿Hay alguna manera de que # 1 pueda estar sucediendo? ¿Alguna configuración SQL2005 / 8 que hace que no admita notificaciones monofásicas?
Actualización 2:
Vuelva a investigar, personalmente, las versiones de SQL Server de todos: "Dev 3" en realidad tiene SQL2008, y "Dev 4" es en realidad SQL2005. Eso me enseñará a no confiar nunca más en mis compañeros de trabajo. ;) Debido a este cambio en los datos, estoy bastante seguro de que hemos encontrado nuestro problema. Nuestros desarrolladores de SQL2008 no estaban experimentando el problema porque SQL2008 tiene una gran cantidad de increíbles incluidos que SQL2005 no tiene.
También me dice que debido a que vamos a admitir SQL2005, no podemos usar TransactionScope como lo hemos hecho, y si queremos usar TransactionScope, necesitaremos pasar un solo objeto SqlConnection ... lo que parece problemático en situaciones en las que SqlConnection no se puede pasar fácilmente ... simplemente huele a instancia global-SqlConnection. ¡Banco de iglesia!
Actualización 3
Solo para aclarar aquí en la pregunta:
SQL2008:
- Permite múltiples conexiones dentro de un solo TransactionScope (como se demuestra en el código de muestra anterior).
- Advertencia # 1: Si esas múltiples conexiones SqlConnections están anidadas, es decir, dos o más conexiones SqlConnections se abren al mismo tiempo, TransactionScope se escalará inmediatamente a DTC.
- Advertencia # 2: Si se abre una SqlConnection adicional a un 'recurso duradero' diferente (es decir: un SQL Server diferente), se escalará inmediatamente a DTC
SQL2005:
- No permite conexiones múltiples dentro de un solo TransactionScope, punto. Se intensificará cuando / si se abre una segunda conexión SqlConnection.
Actualización 4
Con el fin de hacer esta pregunta aún más de un lío útil, y sólo por el bien de la claridad más, aquí es cómo se puede obtener SQL2005 a escalar a DTC con una sola SqlConnection
:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Esto simplemente me parece roto, pero supongo que puedo entender si cada llamada a la SqlConnection.Open()
toma del grupo de conexiones.
"¿Pero por qué podría pasar esto?" Bueno, si usa un SqlTableAdapter contra esa conexión antes de que se abra, el SqlTableAdapter abrirá y cerrará la conexión, terminando efectivamente la transacción porque ahora no puede volver a abrirla.
Entonces, básicamente, para usar TransactionScope con SQL2005 con éxito, necesita tener algún tipo de objeto de conexión global que permanezca abierto desde el punto en que se instancia la primera TransactionScope hasta que ya no sea necesario. Además del olor a código de un objeto de conexión global, abrir la conexión primero y cerrarla al final está en desacuerdo con la lógica de abrir una conexión lo más tarde posible y cerrarla lo antes posible.