lunes, 24 de septiembre de 2007

TransactionScope, TableAdapters y el modo desconectado

Si alguna vez has utilizado los TableAdapters para trabajar y te has enfrentado al dilema de qué hacer cuando vas a guardar de forma transaccional, quizas te interese conocer un enfoque no muy conocido por la gente.

Imaginemos el caso de disponer de dos DataTables, cada uno de los cuales dispone de un DataAdapter que le indica qué comandos utilizará ante una inserción, borrado o actualización. Imaginemos ademas, que queremos que las operaciones realizadas de forma desconectada con los DataTables han de ser actualizadas si o si, es decir que o se actualizan los dos DataTables en la BBDD o ninguno.

Vamos a utilizar un par de cositas nuevas que vienen en .NET 2.0, las clases parciales y la clase TransactionScope

La clase TransactionScope nos facilita la labor creando un ámbito transaccional, donde cada operación sobre cualquier origen de datos, va a realizarse de forma transaccional. Esto hemos de tenerlo muy claro cuando trabajemos con esta clase porque hemos de ser conscientes del peligro que podemos sufrir si trabajamos mal con ella.

Si quisiéramos una actualización transaccional de los dos DataTables:

...
using(TransactionScope transaccion = new TransactionScope())
{
//aqui va todo lo que quiero que sea transaccional
TableAdapter1.Update(dataTable1);
TableAdapter2.Update(dataTable2);
...
}
...


Si intentamos hacer esto, lo que vamos a obtener va a ser una pérdida de rendimiento brutal de la aplicación ( o una excepción si no tenemos habilitado el coordinador de transacciones de microsoft en el servidor ). Esto es porque si recordamos, los tableadapters trabajan de forma desconectada con lo que cada llamada al método Update del TableAdapter va a abrir una conexion , utilizarla y cerrarla. TransactionScope como he dicho se va a encargar de que todo sea realizado bajo una única transacción, pero como resulta que tendremos dos conexiones diferentes contra un origen de datos, él lo escala al coordinador de transacciones de microsoft , lo cual no es bueno ( en este escenario donde realmente no nos hace falta ).

Lo que queremos entonces es que todo se realice bajo la misma conexión y ahi es donde nos podemos encontrar el problema.

Afortunadamente, en ADO.NET 2.0, disponemos del modificador de clase "partial", que nos permite programar una clase en varios archivos. En principio, para los tipos duros que programen los TableAdapters "a pelo" o no tipados, esta parte no les interesa ya que como lo hacen a pelo, ellos mismos tienen acceso a la parte "private" de la clase, pero para los que queremos ir a lo práctico y utilizamos el diseñador del Visual Studio 2005, este truco no está nada mal.

Podemos añadir un método que nos asigne al TableAdapter una conexión que le pasemos por parámetro, con lo que el TableAdapter no necesitará ya crear una nueva.

using System;
using System.Data.SqlClient;
namespace Proyecto.DataLayer.dsProyectoTableAdapters
{
public partial class TableAdapter1
{
public void AssignConnection ( SqlConnection con )
{
this._connection = con;
}
}
}


Con estas simples líneas de código, le acabamos de añadir la funcionalidad de especificarle una conexión a un TableAdapter1 ( cuyos InsertCommand, UpdateCommand,... ya tendremos definidos por ejemplo mediante el editor de Visual Studio 2005 ).

Ahora solo falta utilizarlo y para ello , solamente hay que tener presente que la conexión la tendremos que asignar a los TableAdapters que queramos tener involucrados en la transacción ( insisto, esto es para que no nos escale TransactionScope al coordinador de transacciones de Microsoft en los casos en los que no sea necesario ).

La modificacion anterior quedaria de la siguiente manera:

...
// Creamos el objeto de conexión a la BBDD
SqlConnection conexion = new SqlConnection(cadenaconexioon);
// Asignamos ese objeto de conexión a todos los TableAdapters que queramos que funcionen
// en la misma transacción
TableAdapter1.AssignConnection(conexion);
TableAdapter2.AssignConnection(conexion);
// Abrimos un ámbito transaccional y dentro metemos las llamadas a los métodos Update de los TableAdapters
using(TransactionScope transaccion = new TransactionScope())
{
conexion.Open();
//aqui va todo lo que quiero que sea transaccional
TableAdapter1.Update(dataTable1);
TableAdapter2.Update(dataTable2);
...
conexion.Close();
}
...


Existen tambien métodos para trabajar de forma transaccional mediante Enterprise Library 3.0 y el espacio de nombres TransactionScope que pueden resultarte mas fáciles, asique , esto depende del gusto del consumidor. Solo puedo decirte que este tipo de configuración de la capa de negocio funciona a las mil maravillas en entornos de producción.

No hay comentarios: