miércoles, agosto 27, 2008

Uso de Transacciones en C# con TableAdapters

Actualización (10-10-2008): el enlace correcto al artículo de Mike Pagel es http://www.codeproject.com/KB/dotnet/transactionta.aspx. Gracias Diego.
Actualización: ha habido un error con el enlace el artículo de Mike Pagel, podéis descargaros su clase desde la dirección http://www.nidea-soluciones.com/PruebasAJAX/TransactionSupport.zip

En este nuevo artículo voy a abordar el tema de la gestión de transacciones cuando trabajamos con TableAdapters generados por Visual Studio. El trabajo con TableAdapters generados por Visual Studio es muy cómodo, en el sentido que genera toda la lógica de acceso a datos y la representación de los mismos con Datatables, pero en el momento en que queremos utilizar transacciones en nuestros accesos a base de datos, el tema se complica. En determinados entornos, sobre todo a la hora de programar sistemas grandes, el uso de transacciones se vuelve inevitable. En este artículo vamos a ver una forma muy cómoda de usar transacciones con TableAdapters generados por Visual Studio.

Un poquito de teoría
Antes de nada, vamos a ver un poco cómo va el tema de las transacciones. ¿Para qué sirven las transacciones? Una transacción lo que nos permite es controlar un grupo de sentencias a base de datos de forma que podamos o bien dar marcha atrás a todas las sentencias o bien confirmar el bloque completo. El control del grupo de sentencias lo lleva la base de datos, de forma que el programador sólo debe indicar si se confirma (Commit Transaction) o no (Rollback Transaction) el grupo de sentencias. En el siguiente dibujo os pongo un ejemplo:





Como podéis ver, desde el momento en el que iniciamos la transacción, todas las operaciones quedan registradas y si realizamos un rollback deshacemos las hechas y si hacemos un commit, la transacción se termina y los cambios quedan confirmados.
Pero, ¿qué pasa si hacemos un select sobre un campo actualizado en una transacción no confirmada? Buena pregunta, porque se plantea un problema, si la sentencia no está confirmada, el gestor de base de datos no puede asegurar que ese dato sea correcto. En ese caso entran en juego los niveles de aislamiento de transacciones (Isolation Levels) que, básicamente, lo que deciden es cómo se debe comportar el gestor de base de datos en estas situaciones.

Bueno, al lío
Vamos a ir paso a paso, lo primero que vamos a hacer es crearnos una aplicación de Windows Forms en C# (me supongo que sabéis hacerlo). Para todo vamos a utilizar la versión gratuita (Express) de visual studio, en concreto Visual C# 2005 Express Edition.

1. Crearse un proyecto "Aplicación para Windows"
2. En la pantalla principal nos creamos los siguientes controles











3. Nos creamos una nueva base de datos en SQL Server 2005 Express Edition que se llame "PruebaTransacciones" con una tabla llamada "Tabla1" que contenga 2 campos "Id" y "Nombre" y otra llamada "Tabla2" con los mismos campos. La definición es la siguiente:
























4. Nos creamos en nuestro proyecto dos TableAdapters, uno para cada una de las tablas que hemos creado en base de datos.















Por ahora, nuestro proyecto y los TableAdapters nos tienen que quedar de la siguiente manera:









Ahora mismo no disponemos de soporte para transacciones porque los tableadapters generados no lo incluyen. Es en este momento cuando le vamos a introducir esa nueva funcionalidad. Vamos a utilizar una clase creada por Mike Pagel y que podéis encontrar en su artículo del CodeProject http://www.codeproject.com/useritems/typed_dataset_transaction.asp. Lo que vamos a hacer es sobreescribir la clase base de los tableadapters con esta que incorpora las funcionalidades de las transacciones. Para ello, incluiremos la clase en nuestro proyecto y en las propiedades de los TableAdapters, pondremos el nombre de la clase en la propiedad BaseClass:










En mi caso le he puesto Transaccion.TransactionSupport porque el namespace de la clase es Transaccion.

Utilizar las transacciones
Ahora que tenemos soporte para transacciones vamos a utilizarlas en nuestra pantalla. Teníamos creados un botón y dos radiobuttons, vamos a programar el evento Click del botón de la siguiente manera:






















Lo que hacemos es insertar un registro en la tabla1 y confirmamos o deshacemos la operación en base a los radiobuttons que se pusieron. Ahora vamos a modificar un poco el código para realizar una operación sobre las dos tablas compartiendo la transacción:
























En este caso, hemos igualado la transacción secundaria con la principal para poder interactuar con varias tablas a la vez. El último paso es: ¿cómo pasar una transacción por diferentes clases y métodos para actuar sobre múltiples tablas sin tenerlo todo en el mismo método? La pregunta es larga pero la respuesta es corta, veamos la siguiente modificación al método del botón:






















En este caso, estamos instanciando la clase ManejadorTabla2 y llamando al método ActualizarTabla para guardar los cambios en la tabla2. A este método, aparte de los registros le estamos pasando la transacción. El código interno del método es el siguiente:






















Lo que hacemos en este método es controlar si se pasa como argumento una transacción o no. Si el método falla, lo que hacemos es devolver un false para el método que controla la transacción decida qué hay que hacer. En caso que no se pase transacción, el método se comporta de forma normal.

Conclusión
Ciertamente, el uso de transacciones en entornos grandes es obligatorio. No nos podemos plantear un sistema sin control de transacciones porque antes o después se haría inviable su programación. Creo que esta solución al problema es muy simple, elegante y efectiva y os animo a probarla. Como siguiente paso para el que guste de estos temas queda la modificación de la clase para adaptarlas a entornos no SQLServer como puede ser MySql. El código es muy sencillo y creo que se podría modificar rápidamente.

En fin espero que os haya gustado este articulillo sobre transacciones.

12 comentarios:

Diego Garay Nef dijo...

Hola

Muy interesante tu articulo, estoy desarrollando en VS 2005 con C# pero para pocket pc, que es bastante similiar, pero quiero tengo dudas de tu ejemplo

1 no entiendo como creas y de que parte llamas a Transaccion.TransactionSupport (¿es una clase?), ya que lo que logre entender de Mike Pagel, tienes que implementar la clase parcial del tableAdapter con lo que aparece al final solamente.

2 de donde llamas a al metodo del tableAdapter BeginTransaction, ya que no se ve en ninguna parte del ejemplo implementado

3 Tendrias algun codigo de ejemplo, para tener todo mas claro por favor me ayudaria mucho ya que estoy contra el tiempo.

gracias y saludos.

Manuel Cardenas Thorlund dijo...

Buenas Diego, ha habido un error en la dirección del artículo de Mike Pagel. Te puedes descargar la clase TransactionSupport.cs desde aquí:

http://www.nidea-soluciones.com/PruebasAJAX/TransactionSupport.zip

1. La clase TransactionSupport.cs (es una clase), la descargas de la dirección. La colocas dentro de tu proyecto (donde quieras) y en la propiedad BaseClass del tableadapter la referencias. En mi caso referencio Transaccion.TransactionSupport porque le puso a la clase el namespace Transaccion.

2. El método BeginTransaction del tableadapter lo llamo justo al empezar el bloque try, antes de empezar a realizar los cambios en base de datos.

3. Lo único que tienes que hacer es:
a) Descargarte la clase de Mike Pagel y guardarla en tu proyecto (poniéndole el namespace que quieras).
b) Modificar la propiedad BaseClass de los tableadapters (en el diseñador) referenciando a la clase TransactionSupport. (No hay que implementar la clase parcial del tableadapter).
c) En tu objeto tableadapter aparecerán los métodos BeginTransaction(), RollbackTransaction() y CommitTransaction() y la propiedad Transaction para referenciar la transacción en si.

Una vez puesta la clase en la propiedad BaseClass de los tableadapters puedes probar con el siguiente código:

MiTableAdapter miAdp = new MiTableAdapter();

try
{
miAdp.BeginTransaction();
// Realiza alguna modificacion con miAdp
miAdp.CommitTransaction();
}
catch
{
miAdp.RollbackTransaction();
}

Juega con el código para forzar los rollbacks o los commits y haz el seguimiento en la base de datos.

Espero que te haya servido mi ayuda. Cualquier otra cosa, aquí estamos.

Diego Garay Nef dijo...

Muchas Gracias

voy a probar eso y te cuento como me fue.


saludos.

Diego Garay Nef dijo...

ese el link verdadero.

http://www.codeproject.com/KB/dotnet/transactionta.aspx?display=Print

saludos.

Diego Garay Nef dijo...

Hola

mira he hecho la migracion a compact framework, pero solo me falta algo y me gustaria saber si me puedes ayudar, tengo problemas con el campo de (public SqlTransaction Transaction)

mas especificamente en

// also set connection of this adapter accordingly:
if (value != null){
Connection = value.Connection;
}

el error es

Error 3 No se puede convertir implícitamente el tipo 'System.Data.Common.DbConnection' en 'System.Data.SqlServerCe.SqlCeConnection'. Ya existe una conversión explícita (compruebe si le falta una conversión)

no como poder hacer la conversion, gracias de antemano.

Manuel Cardenas Thorlund dijo...

Gracias por el link.

Con respecto al error, la clase está completamente hecha hacia SQLServer con el framework 2.0. Al hacer la migración hacia Compact Framework entiendo que deberás reescribir las referencias a las librerias, utilizando en vez de:

using System.Data.SqlClient

el correspondiente en el Compact Framework.

Lo siento pero nunca he trabajado con ese framework.

Diego Garay Nef dijo...

Hola

la libreria System.Data.SqlClient, ya esta importada con 0 problemas, si el problema es que value.Connection retorna un valor de tipo 'System.Data.Common.DbConnection' y no se como convertirlo a 'System.Data.SqlServerCe.SqlCeConnection' que es heredada de la clase anterior, bueno voy a seguir buscando, si recuerdas algo me avisas y muchas gracias por tu ayuda

saludos.

Manuel Cardenas Thorlund dijo...

Hola Diego,
he estado mirando la clase y entiendo que si value.Connection te devuelve 'System.Data.Common.DbConnection' y la clase está esperando 'System.Data.SqlServerCe.SqlCeConnection' deberás convertir la definición de la propiedad de SqlTransaction a SqlCETransaction (o algo parecido), porque la propiedad value.Connection se evalua en base a la definición de la propiedad.

Es decir, en la clase dispones de la propiedad:

public SqlTransaction transaccion
{}

supongo que tendrás que cambiar esa definición por SqlCETransaction, así como el resto de propiedades de la clase.

Es lo que se me ocurre. Espero que te sirva.

Alejandro dijo...

Excelente articulo!. Muchas gracias por la info!

Alfredo Chalé dijo...

Excelente publicacion, gracias es lo que estaba buscando..
me funciono correctamente.
Bien por compartir el conocimiento.
Claro y Entendible.

wolfkat dijo...
Este comentario ha sido eliminado por el autor.
wolfkat dijo...

Hola, que tal. Me han surgido varias ideas para hacer esto mas sencillo. Y si la propiedad SqlTransaction se maneja static, de manera que se pueda pasar tranaparentemente cuando se indica el metodo BeginTransaction(), creo que se puede jugar con cierta logica para hacerla de instancia y de clase para evitar estar pasandola en cada TablaAdapter. Les dejo la inquietud y me parece bueno el post.