TableAdapters Transaccionales sin usar TransactionScope

La verdad es que el mundo de los TableAdapters y los Datasets Tipados abre muchí­simas puertas de cara a la productividad en el desarrollo y a la disminución de tasa de errores a la hora de trabajar contra una fuente de datos.
El problema que nos hemos encontrado muy amenudo es querer utilizarlos transaccionalmente pero sin emplear la herramienta TransactionScope de ADO.NET, y seguir utilizando una conexión y transacción controlada por nosotros mismos. No sé por qué pero en su dí­a intenté hacerlo así­ y no hubo manera y tuve que morir al palo del TransactionScope. Recientemente ha vuelto a surgir la necesidad, y, esta vez con Visual Studio 2008 y el Framework 3.5, parece que la cosa ha funcionado.
No sé si es cosa del Framework 3.5 (en su dí­a lo intenté con la versión 2.0), o que hoy me ha dado por estar líºcido, o las otras veces demasiado poco líºcido, pero hoy con una tonterí­a de código he conseguido trabajar de forma transaccional entre dos TableAdapters que están en dos Datasets distintos, compartiendo la misma conexión y transacción.
Sin más rodeos, el código:

public void InsertMarca(string marca, int veces)
{

SqlTransaction tran = null;
SqlConnection con = null;
try
{
// abrimos una conexión
con = new SqlConnection(“Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\BBDD.mdf;Integrated Security=True;User Instance=True”);
con.Open();
//establecemos que la conexión de la marca es la que hemos creado
MarcaTableAdapter adapterMarca = new MarcaTableAdapter();
adapterMarca.Connection = con;
// establecemos que la conexión del coche es la que hemos creado
CocheTableAdapter adapterCoche = new CocheTableAdapter();
adapterCoche.Connection = con;
// iniciamos una transacción sobre dicha conexión
tran = con.BeginTransaction();
// le pasamos a los adaptadores la transacción que acabamos de crear
adapterMarca.Transaction = tran;
adapterCoche.Transaction = tran;
for (int i = 0; i < veces; i++)
{
adapterMarca.InsertMarca(marca);
adapterCoche.InsertCoche(“4444-cxv”, 4, 8);
if (i > 10)
{
// forzamos un error para ver qué pasa
throw new Exception(“prueba de transacción”);
}
}
tran.Commit();
}
catch (Exception ex)
{
tran.Rollback();
}
finally
{
con.Close();
}
}
Aquí­ la pieza que falta es la siguiente, en el diseñador de Datasets, hay que irse a las propiedades de cada uno de los TableAdapter implicados, y establecer la propiedad “ConnectionModifier” a “public”.
Existen otras soluciones a este mismo problema que podéis encontrar descritas en el siguiente artí­culo que explica bastante bien el problema:
http://www.codeproject.com/KB/dotnet/transactionta.aspx
En concreto el autor del artí­culo defiende una solución utilizando Reflection, ya que el otro tipo de solución con la que se suele trabajar para este mismo problema es con el uso de clases parciales.
El problema del uso de las clases parciales es que necesitas generar una clase parcial por cada uno de los TableAdapters, y esto a la larga puede hacer generar mucho código. La solución con Reflection tampoco es la panacea, ya que aunque te evites generar una clase parcial por TableAdapter, aparece el problema de siempre al utilizar Reflection, que nadie te garantiza que tras una actualización del framework la propiedad que has explotado cambie su nombre…
La solución que aporto tiene alguna ventaja, por ejemplo no usa Reflection así­ que nos evitamos problemas de compatibilidades futuras, tampoco es necesario generar una clase parcial por TableAdapter, lo íºnico que tendremos que hacer es en los casos de actuar de forma transaccional (por experiencia el volumen de accesos transaccionales a base de datos no suele llegar ni a la mitad de los accesos de modificación en base de datos) añadir algunas lí­neas más en el código.
Eso sí­, hay una cosa que no me gusta un pelo, que es que tengamos ahí­ en el código la apertura y el cierre de la conexión. Que nos dejemos la apertura lo veo complicado ya que si lo probamos aunque sea una vez nos dará error. El terrible problema lo veo en el cierre de la conexión, ya que es la historia de antaño, como te dejes el finally acabas de reventar tu sistema. Dándole más vueltas, este problemas con una clase que maneje la conexión, que herede de la interfaz IDisposable y que utilicemos mediante un using podrí­amos solventar el problema de olvidarnos cerrar la conexión, pero vamos a seguir teniendo que tragarnos el asignar la conexión y la transacción a todos los adapters que participen.
Saludos.
Miguel.

4 thoughts on “TableAdapters Transaccionales sin usar TransactionScope”

  1. Interesante, pero en el escenario que planteas, ¿por qué se ha de cambiar a “public” la propiedad “ConnectionModifier”?
    Por defecto es “internal”, suficiente para el ejemplo y también para escenarios de assemblies-DAL que encapsulen el acceso a datos (normalmente los TableAdapters no se exponen hacia fuera del assembly).
    ¿No?
    PD: He llegado a tu blog a través de bíºsquedas de información sobre Agile Point.

  2. Es muy buen artí­culo y lo estoy probando, pero tengo una duda, en la parte en la parte donde asignas la transaccion:
    // le pasamos a los adaptadores la transacción que acabamos de crear
    adapterMarca.Transaction = tran;
    adapterCoche.Transaction = tran;
    los tableadapters no tienen la propiedad Transaction q mencionas. No se podrí­a hacer mejor
    adapterMarca.Connection.BeginTransaction()??

  3. Jarid, así­ lo hago yo.
    Extracto del código de mi aplicación:
    Try
    If (TAQuery.Connection.State = ConnectionState.Closed) Then
    TAQuery.Connection.Open()
    End If
    TATechInfo.Connection = TAQuery.Connection
    TAUnitConversor.Connection = TAQuery.Connection
    TAQuery.Transaction = TAQuery.Connection.BeginTransaction()
    TATechInfo.Transaction = TAQuery.Transaction
    TAUnitConversor.Transaction = TAQuery.Transaction
    ‘Hago lo que sea
    TAQuery.Transaction.Commit()
    Catch ex As Exception
    TAQuery.Transaction.Rollback()
    MessageBox.Show(ex.Message, “Error”, MessageBoxButtons.OK, MessageBoxIcon.Error)
    End Try
    Aunque serí­a mejor usar el patrón de diseño Factorí­a o Envelope, y de esta forma crearse una clase con la que inicializar y asignar las transacciones, de los DataAdapters implicados.
    Pienso en algo que deje el código de arriba en esto:
    Try
    mitransaccion = MiFactoria.BeginTrans(TA1, TA2, …)
    ‘ Hago lo que sea
    mitransaccion.Commit()
    Catch ex As Exception
    mitransaccion.Rollback()
    MessageBox.Show(ex.Message, “Error”, MessageBoxButtons.OK, MessageBoxIcon.Error)
    End Try
    Esto es algo que no tengo implementado, pero me pondré a ello porque me hará la vida más cómoda y con menos código, que con esto de .NET parece que vamos para atrás. En vez de facilitarnos la vida, las nuevas librerí­as nos hacen escribir más…
    ¡¡¡ Con lo fácil que era todo esto en ADO o con los TDataSet de Delphi …!!!

  4. Y a todo esto …
    ¿ Me sabéis responder porqué los DataAdapters, por defecto, tienen una conexión diferente ? ¿ No serí­a más cómodo por defecto, compartir la misma conexión, y el que lo desee o así­ quiera para un caso concreto, establecer una conexión distinta para ese DataAdapter ?
    A veces me da la impresión que todas estas cosas nos hacen trabajar más de lo que debiéramos. Vamos si en 1000 bombillas hay 1 apagada y 999 encendidas , ¿ qué es más facil ?
    a) Memorizar la que está apagada.
    b) Memorizar las 999 que están encendidas.
    Pues con todo esto del .NET me parece en ocasiones se nos obliga a memorizar las 999 que están encendidas, en vez de la que estaba apagada como se hací­a y se hace en otros lenguajes.

Leave a Reply to Jarid Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.