LINQ to SQL (4ª Parte) – Actualizando la base de datos

En las últimas semanas he escrito una serie de post sobre LINQ to SQL. LINQ to SQL es un O/RM(object relational mapper) integrado en la versión 3.5 del framework de .NET, y nos permite modelar fácilmente bases de datos relacionales en clases de .NET. Podemos usar expresiones LINQ tanto para consultar la base de datos como para actualizar, insertar y borrar datos.

Aquí tenéis los links a los tres primeros post:

En el post de hoy veremos cómo usar el modelo de datos que hemos creado, y usarlo para actualizar, insertar y borrar datos. También veremos cómo integrar reglas de negocio y crear lógica de validación personalizada con nuetro modelo de datos.

Modelado de la base de datos NorthWind con LINQ to SQL

En la segundo post de esta serie, vimos cómo crear el modelo de clases con el diseñador de LINQ to SQL que trae VS 2008. Aquí tenéis el modelo que creamos a partir de la base de datos de ejemplo Northwind que usaremos en este post:

Cuando definimos el modelo definimos cinco clases: Product, Category, Customer, Order y OrderDetail. Las propiedades de cada clase mapean las diferentes columnas de las tablas correspondientes en la base de datos. Cada instancia de cada clase es una entidad que representa una fila de cada tabal.

Cuando definimos nuestro modelo de datos, el diseñador LINQ to SQL creó una clase llamada DataContext que proporciona todo lo necesario para poder consultar/actualizar  la base de datos. En nuestro ejemplo, esta clase se llama NorthwindDataContext. Ésta clase tiene unas propiedades que representan cada tabla modelada de la base de datos (en concreto: Products, Categories, Customers, Orders y OrderDetails).

Como vimos en el tercer post de esta serie, podemos usar expresiones LINQ para consultar y obtener datos usando la clase NorthwindDataContext.LINQ to SQL traduce automáticamente estas expresiones LINQ al código SQL apropiado en tiempo de ejecución.

Por ejemplo, la siguiente expresión devuelve un objeto Product buscando el nombre del  producto:

La siguiente consulta nos devuelve todos los productos de la base de datos que no han sido pedidos, y cuyo precio es mayor de 100 dólares:

Estamos usando la asociación “OrderDetails” de cada producto como parte de la consulta sólo para obtener aquellos productos que no se han pedido.

Seguir los cambios y DataContext.SubmitChanges()

Cuando creamos consultas y obtenemos objetos como en los ejemplos anteriores, LINQ to SQL estará pendiente de los cambios o actualizaciones que les hagamos a los objetos. Podemos hacer tantas consultas y cambios como queramos usando la clase DataContext de LINQ to SQL, sabiendo que dichos cambios serán supervisados a la vez:

Nota: El seguimiento de cambios de LINQ to SQL ocurre en el lado del consumidor – y NO en la base de datos. Es decir, no estamos consumiendo ningún recurso de la base de datos mientras lo usemos, tampoco tenemos que cambiar/instalar nada en la base de datos para que esto funcione.

Después de realizar los cambios que queramos a los objetos que hemos obtenido con LINQ to SQL, podemos llamar al método “SubmitChanges()” de nuestro DataContext para guardar los cambios en nuestra base de datos. Con esto, LINQ to SQL, creara y ejecutará las sentencias SQL apropiadas para actualizar la base de datos.

Por ejemplo, el siguiente código actualiza el precio y las unidades en stock del producto “Chai” en la base de datos:

Cuando llamamos al método northwind.SubmitChanges(), LINQ to SQL creará y ejecutará las sentencias “UPDATE” de SQL necesarias para guardar las propiedades modificadas.

Con el siguiente código iteramos sobre los productos menos populares y caros y ponemos la propiedad “ReorderLevel” a cero.

Cuando llamamos al método northwind.SubmitChanges(), LINQ to SQL crea y ejecuta las sentencias UPDATE de SQL necesarias para modificar los productos a los que hemos modificado la propiedad ReorderLevel.

Vemos que si no se ha modificado alguna propiedad de un Product con la asignación anterior, LINQ to SQL no ejecutará ninguna actualización para ese objeto. Por ejemplo – si el precio del producto “Chai” era 2 dolares, y el número de unidades en stock era cuatro, la llamada a SubmitChanges() no actualizara esos valores. Sólo los productos cuyo ReorderLevel no era 0 se actualizarán.

Ejemplos de inserción y borrado

Además de poder actualizar la base de datos, LINQ to SQL también nos permite insertar y eliminar datos. Esto lo conseguimos añadiendo o eliminando objectos de las colecciones disponibles en DataContest, y luego llamar al método SubmitChanges(). LINQ to SQL “monitorizará” esas inserciones y borrados, y generará el código SQL necesario cuando se invoque a SubmitChanges()

Añadiendo un producto

Podemos añadir un producto a la base de datos creando un nuevo objeto “Product”, inicializando sus propiedades y añadirlo a la colección “Products” de nuestro DataContext:

Cuando llamemos a SubmitChanges() se añadirá una nueva fila en la tabla de productos.

Borrando productos

De la misma forma que añadimos un nuevo producto a la base de datos añadiendo un objeto Product a la colección Products del DataContext, también podemos borrar productos borrándolos de esa misma colección:

Lo que estamos haciendo es obtener una secuencia de productos “alternados” de la tabla, es decir, no ordenados por ninguna expresión LINQ, y luego esa secuencia se la pasamos al método RemoveAll() de la colección “Products”. Cuando llamamos a SubmitChanges() todos esos productos serán borrados de la tabla

Actualizaciones y relaciones

Lo que hace que los O/RM’s como LINQ to SQL sean tan flexibles es que también nos permiten modelar las relaciones entre las tablas. Por ejemplo, podemos modelar que cada producto tenga una categoría, que cada pedido tenga un detalle de pedido, asociar cada detalle de pedido con un producto, y tener un conjunto de pedidos en cada cliente. Ya vimos cómo modelar las relaciones en la segunda parte de esta serie de post.

LINQ to SQL nos permite aprovechar estas relaciones tanto para consultar como para actualizar nuestros datos. Por ejemplo, con el siguiente código creamos un nuevo producto y lo asociamos con la categoría “Beverages”:

Estamos añadiendo el objeto producto en la colección de categorías de productos. Esto indicará que hay una relación entre dos objetos, y hará que LINQ to SQL mantenga automáticamente las relaciones de clave primaria/ajena entre los dos cuando llamemos a SubmitChanges().

Veamos otro ejemplo para ver cómo LINQ to SQL nos ayuda a mantener limpio el código referente a las relaciones entre las tablas. En el siguiente ejemplo estamos creando un nuevo pedido para un cliente existente. Después de rellenar las propiedades necesarias, podemos crear dos objetos de detalles de pedido y asociarlo a un pedido de un cliente y actualizaremos la base de datos con todos los cambios:

Como vemos, el modelo de programación que hemos usado para hacer todo esto es realmente limpio y orientado a objetos.

Transacciones

Una transacción es un servicio de la base de datos que garantiza que un conjunto de acciones individuales van a suceder de forma atómica – es decir, o se pueden completar todas o si hay alguna que falle, todas las demas se descartarán, y el estado de la base de datos será el mismo que ántes de comenzar la transacción.

Cuando llamemos a SubmitChanges(), las actualizaciones se mapean en una única transacción. Es decir, la base de datos no tendrá nunca un estado inconsistente si hacemos muchos cambios – tanto si se hacen las actualizaciones como si no.

Si no hay ninguna transacción en curso, el objeto DataContext empezará una transacción de la base de datos para guardar las actualizaciones que hagamos con SubmitChanges(). Pero LINQ to SQL también nos permite definir explícitamente y usar nuestro propio sistema de transacciones (introducido en la versión 2.0 de .NET). Esto hace más fácil aún integrar código LINQ to SQL con el código de acceso a datos que ya tengamos. También nos permite encolar recursos que no son propios de la base de datos en la misma transacción – por ejemplo: podemos enviar un mensage MSMQ, actualizar el sistema de archivos (usando el nuevo soporte transaccional de sistemas de archivos), etc – y enlazar todas estas tareas en una sola transacción a la hora de actualizar la base de datos

Validación y lógica de negocio

Una de las cosas más importantes que los desarrolladores tienen que hacer cuando trabajan con datos es incorporar validación  y reglas de negocio. LINQ to SQL tiene varias formas para hacer que los desarrolladores puedan hacer eso de forma fácil y clara.

LINQ to SQL nos permite añadir esta validación lógica una vez. De forma que no tendremos que repetir esa lógica en varios sitios, con lo que conseguimos un modelo de datos más mantenible y más claro.

Soporte de validación de esquemas

Cuando definimos el modelo de clases de datos con el diseñador de LINQ to SQL de VS 2008, se añadirán algunas reglas de validación obtenidas del esquema de las tablas de la base de datos.

Los tipos de datos de las propiedades de las clases del modelo de datos coincidirán con el esquema de la base de datos. Con esto tendremos errores de compilación si intentamos asignar un booleano a un valor decimal, o si convertirmos tipos numéricos incorrectamente.

Si una columna en la base de datos está marcada como nullable, la propiedad correspondiente que crea el diseñador de LINQ to SQL será un tipo nullable. Las columnas marcadas como no nullables lanzarán excepciones si no les asignamos ningun valor. LINQ to SQL también se asegurará que de que los valores identidad/unicos se asignan correctamente.

Obviamente podemos usar el diseñador LINQ to SQL para sobreescribir los valores por defecto del esquema si queremos – pero por defecto, las tendremos automáticamente sin tener que hacer nada. LINQ to SQL también comprueba los valores de los parámetros de las consultas SQL, de manera que no tendremos que preocuparnos por los ataques de inyección de SQL.

Soporte para validación personalizada de propiedades

La validación de datos a través de esquemas es muy útil, pero no suele ser suficiente en escenarios reales.

Imaginemos que en la base de datos Northwind tenemos una propiedad “Phone” en la clase “Customer” que está definida en la base de datos como nvarchar. Usando LINQ to SQL podemos escribir el siguiente código para actualizarlo con un número de teléfono válido:

El problema que nos encontraríamos, sería que el siguiente código sigue siendo válido desde el punto de vista de un esquema SQL (ya que sigue siendo una cadena, no un número de teléfono válido).

Para no permitir que no se puedan meter números de teléfono erróneos en nuestra base de datos, podemos añadir una regla de validación personalizada a la clase Customer de nuestro modelo de datos. Es realmente fácil, todo lo que necesitamos hacer es añadir una nueva clase parcial a nuestro proyecto que defina el siguiente método:

Este código usa dos caracteristicas de LINQ to SQL:

  1. Todas las clases que genera el diseñador LINQ to SQL son “parciales” – es decir, podemos añadir métodos adicionales, propiedades, y eventos (en archivos separados). Así podemos extender nuestro modelo de clases creada por el diseñador de LINQ to SQL con reglas de validación y métodos auxiliares que definamos. No es necesario ninguna configuración.
  2. LINQ to SQL expone una serie de puntos de extensión en el modelo de datos que podemos usar para añadir validación lógica. Muchos de estos puntos de extensión usan la nueva característica llamada “métodos parciales” que viene con VB y C# en VS 2008 Beta2. Wes Dyer el equipo de C# ha escrito un post explicando cómo va esto de los métodos parciales.

En nuestro ejemplo de validación, estamos usando el método parcial OnPhoneChangin que se ejecuta cada vez que se cambia el valor de la propiedad “Phone” de un objeto “Customer”. Podemos usar este método para validar la entrada de datos (en este caso estamos usan una expresión regular). Si todo va bien, LINQ to SQL asumirá que el valor es válido. Si hay algún problema con el valor, podemos lanzar una excepción en el método de validación – que hará que la asignación no se haga.

Soporte para validación personalizada de objetos entidad.

En el punto anterior hemos visto cómo añadir validación a una propiedad individual de nuestro modelo de datos. Sin embargo, algunas veces, necesitamos/queremos validar validar multiples propiedades de un objeto.

Veamos un ejemplo, tenemos un objeto Order y queremos poner las propiedades “OrderDate” y “RequiredDate”:

Este código es legal desde el punto de vista de SQL – aunque no tenga ningún sentido la propiedad de fecha de entrega, que era para ayer.

LINQ to SQL en Beta2 nos permite añadir reglas de validación a nivel de entidad para corregir este tipo de errores. Podemos añadir una clase parcial para nuestra entidad “Order” e implementar el método parcial OnValidate() que se invocará ántes de que se guarden los datos en la base de datos. De esta forma, podemos acceder y validar todas las propiedades de nuestro modelo de datos:

De esta forma podemos validar cualquiera de las propiedades de la entidad (incluso obtener acceso de sólo lectura a los objetos asociados), y lanzar una excepción si el valor es incorrecto. Cualquier excepción lanzada desde el método OnValidate() abortará cualquier cambio que queramos hacer en la base de datos, y deshacer todos los cambios hechos en la transacción actual.

Validación en los métodos de inserción/actualización/borrado.

A menudo necesitamos añadir validación específica en los métodos de inserción, actualización o borrado. LINQ to SQL nos lo permite añadiendo una clase parcial que extienda a la clase DataContext e implementar métodos parciales para personalizar la lógica de inserción, actualización y borrado de las entidades de nuestro modelo de datos. Estos métodos serán llamados automáticamente cuando invoquemos a SubmitChanges().

Podemos añadir la validación lógica que estimemos oportuna con estos métodos – y si todo va bien, LINQ to SQL continará guardando los datos en la base de datos (llamando al método de DataContext “ExecuteDynamicXYZ”).

Podemos añadir métodos que se invocarán automáticamente cuando se vayan a crear/actualizar/borrar datos. Por ejemplo, supongamos que queremos crear un nuevo pedido y asociarlo con un cliente existente:

Cuando llamamos a northwind.SubmitChanges(), LINQ to SQL determinará que es necesario guardar el nuevo objeto Order, y ejecutará nuestro método parcial “InsertOrder”.

Avanzado: Viendo la lista de cambios de la transacción

Hay veces que no nos interesa añadir validación lógica a elementos individuales, sino que queremos ser capaces de ver toda la lista de cambios que están ocurriendo en una transacción.

Desde la Beta2 de .NET 3.5, LINQ to SQL nos permite acceder a la lista de cambios a través del método DataContext.GetChangeList(). Nos devolverá un objeto ChangeList que expone una serie de colecciones de adiciones, borrados y modificaciones que se han hecho.

Una aproximación que podemos hacer en algunos escenarios es crear una clase parcial de la clase DataContext y sobreescribir su método SubmitChange(). Podemos obtener la lista de ChangeList() para las operaciones de actualizaciones y crear cualquier validación que queramos:

Este ejemplo es un caso de uso avanzado – pero es interesante saber que siempre podremos extender y aprovecharnos de esta forma de las nuevas características de LINQ to SQL.

Administrando cambios simultáneos con concurrencia optimista.

Una de las cosas en las que tenemos que pensar los desarrolladores en entornos multi-usuarios es cómo administrar las actualizaciones de los mismos datos en la base de datos. Por ejemplo, imaginemos que tenemos dos usuarios que obtienen un objeto product, y uno de ellos cambia el ReorderLevel a 0 mientras que el otro lo pone a 1. Si ambos usuarios guardan esos cambios en la base de datos, el desarrollador tiene que decidir cómo tratar ese conflicto.

Una solución es dejar que sea el último que lo guarda – es decir, que el valor que el primer usuario guardó se perderá sin que éste se de cuenta. Esta es una solución muy pobre (e incorrecta).

Otra solución que permite LINQ to SQL es usar el modelo de concurrencia optimista, es decir, LINQ to SQL detectará automáticamente si el valor original de la base de datos ha sido actualizado por alguien ántes que se guarden los nuevos datos. LINQ to SQL nos da una lista de conflictos de valores cambiados al desarrollador y nos permite tanto hacer lo que queramos como avisar al usuario de la aplicación para que nos indique el propio usuario lo que quiere hacer.

Ya veremos en más detalle este tema en un próximo post.

Uso de procedimientos almacenados o lógica SQL personalizada para insertar, actualizar y borrar.

Una de las preguntas que tienen los desarrolladores (en especial los DBAs), que suelen escribir procedimientos almacenados con SQL personalizadas, cuando ven LINQ to SQL por primeravez es: “¿pero cómo podemos tener control absoluto del SQL que se está ejecutando?”.

Las buenas noticias son que LINQ to SQL tiene un modelo muy flexible que nos permite sobreescribir el SQL que se está ejecutando, y llamar a los procedimientos almacenados que desarrollemos para añadir, actualizar o borrar datos.

Lo realmente increible es que podemos empezar definiendo nuestro modelo de datos y dejar que LINQ to SQL administre las inserciones, actualizaciones y borrados. Una vez hecho esto, podemos personalizar el modelo de datos para que use nuestros propios procedimientos almacenados o nuestras sentencias SQL – sin tener que cambiar nada de la lógica de aplicación que estamos usando para nuestro modelo de datos, ni cambiar nada de las validaciones ni de la lógica de negocio. Esto nos da una gran flexibilidad a la hora de construir nuestra aplicación.

Dejaremos para otro post cómo personalizar los modelos de datos con procedimientos almacenados o sentencias SQL.

Resumen.

Este post presenta un buen resumen sobre cómo podemos usar LINQ to SQL para actualizar nuestra base de datos e integrar de una forma clara validación de datos y lógica de negocio. Creo que encontraréis que LINQ to SQL incrementa mucho la prouctividad a la hora de trabajar con datos, y nos permite escribir código orientado a objeto claro en el acceso a datos.

En próximos post veremos el nuevo control <asp:linqdatasource> de la versión 3.5 de .NET, y hablaremos sobre lo fácil que es crear interfaces de usuario en ASP.NET que se aprovechen de los modelos de datos de LINQ to SQL. También veremos algunos conceptos de programación más especificos de LINQ to SQL sobre concurrencia optimista, carga perezosa, herencia de mapeado de tablas, uso de procedimientos almacenados y sentencias SQL personalizadas, y mucho más.

Espero que sirva.

Scott.

Traducido por: Juan María Laó Ramos. Microsoft Student Partner.

toH tlhIngan Hol DajatlhlaH ‘e’ DaneH’a’?