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.