30 marzo 2007

Implementando SqlTransaction en un TableAdapters...

Saludos amigos...

Les comento algo antes de entrar al tema, yo trabaje mucho con DAAB (Data Access Application Block) me estudie lo básico del DAAB y mis pocas aplicaciones que he implante me funcionaron, recientemente comencé un nuevo proyecto y decidí hacerlo desde cero, haber que cosas se me ocurrían de nuevo y salir un poco de los código rutinarios – copiar y pegar - (ustedes saben cuando uno ya forma parte de un comunidad global – labloquera.net – ya se cree un MVP) y decidí aventurarme con los TableAdapters, hijos míos mayor sorpresa me andado, es increíble el poder que tienes, la facilidad de uso y lo rápido que lo implementas. Todos mis TableAdapters están conectados con Procedimientos Almacenados (Select, Insert, Update y Delete), agregué un nuevo campo a una tabla y actualice los procedimientos almacenado para agregar este nuevo campo (esto lo realice en la base de datos – SQL Server 2005) y después ejecutas un re-configuración del TableAdapter, esto te genera todo de nuevo otra vez y lo mas bonito de esto sabes que es…? No escribiste ni una línea de código…! Como se le podrá llamar a esto Magia o RAD.

TableAdapter Configuration


Esto esto parece increíble y quisas muy difícil, pero realmente es mucho mas facil de lo que se inmaginan. No crean que esto lo descubri yo (no me sente bajo un arbol y una manzana me golpeo la cabeza) simplemente busque en Internet, conseguí varias personas y paginas web que preguntaban y deseaban hacer lo mismo que yo queria. Comenze a probar e investigar la mayoria estaban perdidos, solo unas pocas paginas web se acercaban a esto. Anexo fuente de datos:

http://blah.winsmarts.com/2006/06/18/the-definitive-tableadapters--transactions-blog-post.aspx (escrita por Sahil Malik-“Pro ADO.NET 2.0”)
http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=301804&SiteID=1 (Forum de Microsoft MSDN)

Las transacciones van pegadas a los objetos command de los TableAdapter (Insert, Update, Delete) dependiendo del caso…


SqlTransaction tran = IndiqueLaConnection.BeginTransaction();

this.Adapter.UpdateCommand.Transaction = tran;

this.Adapter.InsertCommand.Transaction = tran;

this.Adapter.DeleteCommand.Transaction = tran;

Les explico como hacer esto en varios pasos…

Paso 1.-
Seleccionamos un DataTable, para este caso nuestra DataTable es Productos y el TableAdapter que trabajaremos es ProductosTableAdapter.

ProductosTableAdapter

Nota: La forma de trabajar los TableAdapters, es mediante las clases parciales (partial class) todo los que escribamos en el archivo .Designer.cs, se perderá en cualquier cambio o pestañeo que se realice el DataSet, debido a que este es un archivo generado.

Soluction Explorer

Y Como se crean una clase parcial (partial class) de un TableAdapter..?
Sencillo haciendo Doble Click con el raton – Mouse – en los metodos “Fill,GetData()” o en cualquier otro. Automáticamente se abrirá un archivo que contiene nuestra clase parcial.

namespace Admin.DAL._ProductosTableAdapters
{
public partial class ProductosTableAdapter : System.ComponentModel.Component
{
}
}

Aquí es donde añadiremos el código para manejar a nuestro gusto el TableAdapter (crearemos un mostró como frankiTableAdapter – esperemos que no se nos salga de control.)

FrankiTableAdapter

Paso 2.-
Agregar los métodos que nos ayudaran a controlar la transacción, los métodos para abrir la conexión, cerrar la conexión y comenzar la transacción. Debemos recordar que los TableAdapter controlan nuestra bodega de datos (ellos compran por nosotros, venden por nosotros, reciben el cambio por nosotros y nos entregan el producto a nosotros), es decir abre la conexión a la base de datos, ejecuta la instrucción (select,insert,update,delete) o nos devuelve los datos y por ultimo cierra la conexión. Y estos nuevos métodos los agregaremos dentro de nuestra clase parcial que vimos anteriormente.

namespace Admin.DAL._ProductosTableAdapters

{

public partial class ProductosTableAdapter : System.ComponentModel.Component

{

// Abrir la conexion a la base de datos

public SqlConnection OpenConnection()

{

// Abre la conexion a la base de datos en caso de estar cerrada

if (_connection.State != System.Data.ConnectionState.Open)

_connection.Open();

// retorna la conexion

return _connection;

}

// Cierra la conexión

public void CloseConnection()

{

// si se dan cuenta no he definido la variable "_connection"

// es porque me la estoy utilizandola de la clase parcial

// que ya se definio y se encuentra en el archivo productos.Designer.cs

_connection.Close();

}


// Arrancamos la transacción

public SqlTransaction BeginTransactionUpdate(SqlConnection IndiqueLaConnection)

{

// Verificamos de nuevo el estado de la conexion - soy un paranoico -

if (IndiqueLaConnection.State == System.Data.ConnectionState.Closed)

{

throw new Exception("La conección esta cerrado, por lo tanto no se puede realizar una nueva transacción");

}

// Comensamos una nueva transaccion a partir de la conexión que estamos pasando

// esta la asignamos a una variable "tran" de SqlTransaccion

SqlTransaction tran = IndiqueLaConnection.BeginTransaction();

// this.Adapter => es un metodo definido por el TableAdapter

// dentro del archivo Productos.Designer.cs

// Asignamos al metodo ".Transaction" de cada uno de los comandos

// (insert, update, delete) del TableAdapter el objeto SqlTransaccion

// contenido en la variable "tran"

this.Adapter.UpdateCommand.Transaction = tran;

this.Adapter.InsertCommand.Transaction = tran;

this.Adapter.DeleteCommand.Transaction = tran;

// Y por ultimo retornamos el objeto SqlTransaccion

// si alguien mas la necesita...

return tran;

}

}

}

Paso 3.-

Como implementamos esto dentro de nuestro código, de esta forma;

void tbttAceptar_Click(object sender, EventArgs e)
{
ProductosTableAdapter
elProducto = new ProductosTableAdapter();
System.Data.SqlClient.SqlConnection tableAdapConn = null;
System.Data.SqlClient.SqlTransaction tableAdapTran = null;
try
{
tableAdapConn = this.elProducto.OpenConnection();
tableAdapTran = this.elProducto.BeginTransactionUpdate(tableAdapConn);
// Colocas en este espacio tu transaccion
this.elProducto.Update(this._Productos.Productos);
tableAdapTran.Commit();
}
catch (Exception ex1)
{
MessageBox.Show(ex1.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error);
tableAdapTran.Rollback();
}
finally
{
tableAdapConn.Close();
}
}

Paso 4.-
Lo mas probable es que tengas otros TableAdapter (DepartamentosTableAdapter, ProveedoresTableAdapter, etc) y es realmente cuando se debe utilizar transacciones porque estas trabajando con varias actualizaciones en un solo bloque, si alguna actualización falla se debe ejecutar un rollback() y con esto nos reversara las actualizaciones anteriormente realizadas a la falla (ya sea un insert, un delete o un update). El código seria el siguiente, por supuesto añadiendo a la clase partial de los TableAdapter lo anteriormente descrito en el paso 1 y añadiendo un nuevo metodo al paso 2.

Agregar nuevo método al Paso 2 – TransaccionUpdate (sqlTransaccion xxxx) para este caso trabajaremos con otro TableAdapter (DepartamentosTableAdapter)


public partial class DepartamentosTableAdapter : System.ComponentModel.Component

{

// Abrir la conexion a la base de datos

public SqlConnection OpenConnection()…

// Cierra la conexión

public void CloseConnection()…

// Arrancamos la transacción

public SqlTransaction BeginTransactionUpdate(SqlConnection IndiqueLaConnection)…

// Utilizamos la transaccion anterior

public bool TransactionUpdate(SqlTransaction miTransaccion)

{

bool reval = false;

if (miTransaccion.Connection.State == System.Data.ConnectionState.Open)

{

this.Adapter.UpdateCommand.Transaction = miTransaccion;

this.Adapter.InsertCommand.Transaction = miTransaccion;

this.Adapter.DeleteCommand.Transaction = miTransaccion;

reval = true;

}

return reval;

}

}

Este método lo su única función es utilizar un transacción existente y asignarla al método “Transacción” de los comandos del TableAdapter. Y como se implementa es nuevo código, de la siguiente manera…


System.Data.SqlClient.SqlConnection tableAdapConn = null;

System.Data.SqlClient.SqlTransaction tableAdapTranProd = null;

try

{

tableAdapConn = this.elProducto.OpenConnection();

tableAdapTranProd = this.elProducto.BeginTransactionUpdate(tableAdapConn);

this.elDepartamento.TransactionUpdate(tableAdapTranProd);

// Colocas en este espacio tu transaccion

this.elProducto.Update(this._Productos.Productos);

this.elDepartamento.Update(this._Productos.Departamentos);

// el objeto tableAdapTranProd control la transaccion de

// elProducto y elDepartamento (ambos TableAdapter)

tableAdapTranProd.Commit();

}

catch (Exception ex1)

{

MessageBox.Show(ex1.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error);

tableAdapTranProd.Rollback();

}

finally

{

tableAdapConn.Close();

}

Hay que recordar que ambos TableAdapters estan dentro de un DataSet y por lo tanto poseen la misma conexión a la base de datos y eso me permite utilizar la misma Transacción.

DataSet - Productos

En caso de tener conexiones diferentes o apuntaran a diferentes bases de datos, tendría que aplicar otra arquitectura de transacción como MSDTC (Microsoft Distributed Transaction Coordinator) mediante el namespace “System.Transactions”, pero como dice el final de toda historia ¡Ese es otro articulo, que estaré hablando en otro post.!

Y Gracias por leer...

Nota: Disculpen el mal formato del codigo que sé que dificulta un poco la comprencion, realmente no se como acomodarlo si alguien me ayuda, se lo agradeceria.
Tambien lo pueden leer de la bloguera.net creo que esta un poco mejor...

1 comentario:

Diego Garay Nef dijo...

Hola

Primero que nada muy bueno tu articulo.

Tengo dos preguntas

ocupo VS 2005 con c# y no puedo acceder al famoso campo _connection, this.Adapter ¿como lo haces? ya que ese campo se encuentra en el designer del datatable y no permite referenciarlo.

Tiene el codigo fuente para probarlo

saludos.