transacciones - transactionscope c# oracle
TransactionScope: evitar transacciones distribuidas (3)
Tengo un objeto principal (parte de un DAL) que contiene, entre otras cosas, una colección ( List<t>
) de objetos secundarios.
Cuando guardo el objeto en la base de datos, ingreso / actualizo el padre y luego recorro cada niño. Para la mantenibilidad, he puesto todo el código para el niño en un método privado separado.
Iba a usar Transacciones ADO estándar, pero en mis viajes, tropecé con el objeto TransactionScope, que creo que me permitirá ajustar toda la interacción DB en el método padre (junto con toda interacción en el método secundario) en una transacción.
Hasta aquí todo bien..?
Entonces, la siguiente pregunta es cómo crear y usar conexiones dentro de este TransactionScope. He oído que el uso de conexiones múltiples, incluso si están en la misma base de datos, puede obligar a TransactionScope a pensar que se trata de una transacción distribuida (que implica un costoso trabajo de DTC).
¿Es el caso? ¿O es que, como parece estar leyendo en otro lado, un caso que usar la misma cadena de conexión (que se prestará a la agrupación de conexiones) estará bien?
Más en términos prácticos, ¿yo ...
- Crear conexiones separadas en el padre y el hijo (aunque con la misma cadena de conexión)
- Crear una conexión en el padre y pasarla como parámetro (me parece torpe)
- Hacer algo más...?
ACTUALIZAR:
Si bien parece que estaré bien utilizando mi habitual .NET3.5 + y SQL Server 2008+, otra parte de este proyecto utilizará Oracle (10g), así que también podría practicar una técnica que se pueda usar de manera consistente en todos los proyectos.
Así que simplemente pasaré la conexión a los métodos secundarios.
Opción 1 Ejemplo de código:
using (TransactionScope ts = new TransactionScope())
{
using (SqlConnection conn = new SqlConnection(connString))
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.Connection = conn;
cmd.Connection.Open();
cmd.CommandType = CommandType.StoredProcedure;
try
{
//create & add parameters to command
//save parent object to DB
cmd.ExecuteNonQuery();
if ((int)cmd.Parameters["@Result"].Value != 0)
{
//not ok
//rollback transaction
ts.Dispose();
return false;
}
else //enquiry saved OK
{
if (update)
{
enquiryID = (int)cmd.Parameters["@EnquiryID"].Value;
}
//Save Vehicles (child objects)
if (SaveVehiclesToEPE())
{
ts.Complete();
return true;
}
else
{
ts.Dispose();
return false;
}
}
}
catch (Exception ex)
{
//log error
ts.Dispose();
throw;
}
}
}
}
Empíricamente, he determinado que (para el proveedor de SQL Server) si el proceso puede aprovechar la agrupación de conexiones para compartir la conexión (y la transacción) entre los procesos padre e hijo, el DTC no necesariamente se verá involucrado.
Este es un gran "si", sin embargo, según su ejemplo, la conexión creada por el proceso principal no puede ser compartida por los procesos secundarios (no se cierra / libera la conexión antes de invocar los procesos secundarios). Esto dará lugar a una transacción que abarca dos conexiones reales, lo que dará lugar a que la transacción se promueva a una transacción distribuida.
Parece que sería fácil refactorizar su código para evitar este escenario: simplemente cierre la conexión creada por el proceso principal antes de invocar los procesos secundarios.
En su ejemplo, TransactionScope todavía se encuentra en el contexto de un método, simplemente podría crear una SqlTransaction con múltiples comandos debajo de eso. Use TransactionScope si desea mover la transacción fuera de un método, para decir, la persona que llama de ese método, o si accede a múltiples bases de datos.
Actualización: no importa, acabo de ver la llamada del niño. En esta situación, podría pasar el objeto de conexión a clases secundarias. Además, no es necesario que deseche manualmente el TransactionScope, ya que los bloques actúan como bloques try-finally y ejecutarán el desecho incluso con excepciones.
Actualización 2: mejor aún, pase el IDbTransaction a la clase infantil. La conexión se puede recuperar de eso.
Muchos proveedores de ADO de base de datos (como Oracle ODP.NET) comienzan transacciones distribuidas cuando usa TransactionScope
para realizar transacciones a través de múltiples conexiones, incluso cuando comparten la misma cadena de conexión.
Algunos proveedores (como SQL2008 en .NET 3.5+) reconocen cuándo se crea una nueva conexión en un ámbito de transacción que hace referencia a la misma cadena de conexión y no da como resultado el trabajo de DTC. Pero cualquier variación en la cadena de conexión (como los parámetros de ajuste) puede impedir que esto ocurra, y el comportamiento volverá a usar una transacción distribuida.
Desafortunadamente, el único medio confiable para garantizar que sus transacciones funcionen juntas sin crear una transacción distribuida es pasar el objeto de conexión (o el IDbTransaction
) a métodos que necesitan "continuar" en la misma transacción.
A veces ayuda a elevar la conexión a un miembro de la clase en la que está haciendo el trabajo, pero esto puede crear situaciones incómodas y complica el control de la vida útil y la eliminación del objeto de conexión (ya que generalmente excluye el uso de la declaración de using
)