Wednesday, July 20, 2011

LINQ to SQL. Problemas y errores comunes (parte 1)

Implementación de Transacciones

Para los que trabajamos con Visual Studio.Net y hemos venido siguiendo su desarrollo desde sus inicios la llegada de “LINQ to SQL” supuso un paso de avance en el trabajo con bases de datos en Sql Server. Pero la migración a esta tecnología no está exenta de problemas y errores que muchas veces cometemos por no leer a fondo sus peculiaridades o por falta de tiempo para hacerlo.

Este artículo está destinado a usuarios que ya conocen y trabajan con esta tecnología. No pretende ser ni mínimamente una guía para principiantes.

LINQ (sin SQL) es un lenguaje de consulta muy potente y su gran ventaja es que puede ser aplicado a simples listas en memoria aun cuando su origen no sea desde bases de datos.

Este uso sobre simples listas en memoria (LINQ to Object) fue el primero que le di a esta tecnología en un proyecto que realizábamos hace ya algún tiempo. Aquí fue donde aprendí (junto con mi equipo) la sintaxis de cada sentencia y sus equivalentes en expresiones Lambda que casi siempre usamos.

Después lo comenzamos a usar en proyectos de ASP.Net con problemas menores que poco a poco fuimos solucionando y nos llevó a ganar en experiencia.

Pero la propia naturaleza de los proyectos Webs (todo corre centralizadamente en el servidor, por lo general se trabaja con una entidad a la vez en cada página) nos hizo muy fácil trabajar con una tecnología que no habíamos leído en sus aspectos más profundos referidos a acceso concurrente a datos y tratamiento de transacciones.

El verdadero dolor de cabezo vino recientemente en una aplicación Desktop que debía tener corriendo varias instancias a la vez modificando y compartiendo datos comunes a todas. En específico se trataba de una clásica aplicación de Venta y Control de Inventario con varios Puntos de Ventas en paralelo.

Cuando leemos sobre LINQ enseguida nos llevamos la idea que es un modelo de Objetos en memoria que mapea la estructura de la BD, de la cual permanece desconectada, que implementa operaciones de manipulación de datos y que mantiene una Cache de datos para evitar el tráfico innecesario y manipular de forma eficiente las operaciones. Dichas operaciones corren en un ambiente implícito de transacciones. Todo eso es bonito pero lo que no es bonito es la interpretación y el entendimiento que hacemos de ello con lo que comienzan a surgir los problemas más serios.

Transacciones implícitas, no tan implícitas.

Transacciones implícitas se traduce en la vida real en que las operaciones que hagamos sobre una misma tabla o entidad se ejecutarán en una transacción (todo o nada) pero no se refiere para nada a que si hacemos operaciones sobre más de una Entidad a la vez y una de ellas falla en el intermedio del conjunto pues se deshagan todos los cambios desde el inicio.

Aquí es donde comenzamos a implementar la primera versión de un modelo de transacciones, como el siguiente:


public static class DataHelper
{
public static IDbTransaction BeginTransaction(AdminDataContext dataContext)
        {
            if (dataContext.Connection.State == ConnectionState.Closed)
            {
                dataContext.Connection.Open();
            }

            dataContext.Transaction = dataContext.Connection.BeginTransaction();

            return dataContext.Transaction;
        }

        public static void CommitTransaction(AdminDataContext dataContext)
        {
            if (dataContext.Transaction != null)
            {
                dataContext.Transaction.Commit();
            }
        }

        public static void RollbackTransaction(AdminDataContext dataContext)
        {
            if (dataContext.Transaction != null)
            {
                dataContext.Transaction.Rollback();
            }
        }
}


Su utilización es trivial en un bloque de código similar al siguiente:
protected DataContext dc = DataHelper.GetDataContext();

try
{
    using (DataHelper.BeginTransaction(dc))
    {
        //Operaciones en varias Entidades
        dc.Inventories.InsertOnSubmit(...);
        dc.Kardexes.InsertOnSubmit(...);

        dc.SubmitChanges();

        DataHelper.CommitTransaction(dc);
    }
}
catch (Exception ex)
{
    DataHelper.RollbackTransaction(dc);
}


Esto nos soluciona el primer y más importante problema pero aún nos quedan otros que fuimos detectando en el camino y que incluso nos hicieron cambiar el modelo de Transacciones, aun cuando este funciona.