lunes, 18 de enero de 2010

El misterioso caso de LINQ to SQL y 0=1…

Como ya más o menos todos conocéis LINQ to SQL implementa una política de concurrencia optimista. Esta característica parte de la premisa de no realizar ningún bloqueo sobre los registros con los que estamos trabajando. Este modo es el recomendado, y en ocasiones el único aceptable, sobre todo en entornos de alta escalabilidad como son por ejemplo las aplicaciones Web.

Cuando nos creamos el modelo de nuestra aplicación a partir de la base de datos, generamos un fichero dbml. Es importante tener en cuenta que, entre otras propiedades, podemos especificar como queremos que el contexto gestione el UpdateCheck de cada columna. (Always, WhenChanged, Never).

Esto nos permite, en base a la naturaleza de la información de un campo, afinar la política de comprobación de la concurrencia que deseamos aplicar. Por ejemplo, podemos sobrescribir siempre la descripción detallada de un producto (sin importarnos si otro usuario la ha modificado desde que leímos ) pero puede no ser aceptable para, por ejemplo, el Stock asociado al mismo. Esta característica junto con el RowVerison nos permite detectar los problemas de concurrencia y automatizar su resolución en la medida de lo posible.

Hablando con un equipo de desarrollo me contaban que tenían problemas al borrar un registro. Revisándolo con ellos, me comentaban que pasaban por parámetro clave primaria del registro que deseaban eliminar. El código que empleaban era similar a este:

using (xxxDataContext ctx = new xxxDataContext())
{
      Table1 t1 = new Table1 { Id = id };
        ctx.Table1s.Attach(t1);
        ctx.Table1s.DeleteOnSubmit(t1);
        ctx.SubmitChanges();
}

Y el error que percibían era que no se eliminaba el registro asociado en la base de datos pero no sabían exactamente porqué...

Al revisar la política de UpdateCheck de las columnas de la entidad, me di cuenta que estaban definidos en el modelo con su valor predeterminado. (Always). Por lo que la cosa estaba clara, Linq To SQL estaba creando la consulta que lanzaba al proveedor subyacente concatenándole el valor original y al no encontrar dicho registro en la base de datos no eliminaba nada…

Al comprobar mi teoría veo que la sentencia que genera Linq To SQL para el borrado incluye una clausula where del estilo ...

DELETE FROM [dbo].[Table1] WHERE 0 = 1

¿¿0=1??

Para darles respuesta les comenté el problema y la solución fue tan simple como cambiar el UpdateCheck a Never puesto que este escenario les cuadraba para dicha tabla, pero yo ya me había quedado mosca con el tema del 0=1...

Leyendo un poco descubro que es una optimización generada por el equipo de ADO.net puesto que determinan que nunca se cumple la condición. Y la condición que nunca se cumple es la siguiente:

En la base de datos existe un campo Name que esta definida como NOT NULL, por lo tanto nunca puede almacenar un valor NULL (obvio, no?). En .Net el tipo de la propiedad que mapea a dicho campo es de tipo String y por la naturaleza del mismo este puede almacenar un NULL. De hecho es los que almacena si nos fijamos en el código superior.

Como LINQ To SQL observa que en el objeto el valor es un NULL y en la definición del esquema de la base de datos este valor no puede estar presente, determina que no va a coincidir por lo que optimiza con el 0=1...

Bueno dicho (y comprendido) queda...