LINQ to SLQ (Parte 7 – Actualizando la base de datos con procedimientos almacenados)

En las últimas semanas he escrito una serie de post sobre LINQ to SQL. Es un ORM integrado en .NET 3.5, y nos permite modelar bases de datos relacionales con clases de .NET. Podemos usar expresiones LINQ para consultar a la base de datos, actualiazarla, insertar y borrar datos.

Aquí tenéis los enlaces a los otros post:

En la sexta parte vimos cómo podemos usar procedimientos almacenados (SPROCs) y funciones definidas por el usuario (UDFs) para consultar la base de datos con el modelo de datos de LINQ to SQL. En el post de hoy veremos cómo podemos usar los SPROCs para actualizar/insertar/borrar datos de nuestra base de datos.

Para ayudar a entender esto empezaremos costruyendo una capa de datos para la base de datos de ejemplo Northwind:

Paso 1: Crear nuestra capa de acceso a datos (sin SPROCs)

En la segunda parte de esta serie vimos cómo usar el diseñador de LINQ to SQL de VS 2008 para crear el siguiente modelo de clases:

Añadiendo reglas de validación a nuestro modelo de clases.

Después de definir nuestro modelo querremos añadir reglas de validación a nuestro modelo de datos. Podemos hacer esto añadiendo clases parciales a nuestro proyecto y añadir las reglas de validación en esas clases (vimos cómo hacer esto en la cuarta parte de esta serie).

Por ejemplo, podemos añadir la lógica necesaria para asegurarnos de que el número de teléfono de los clientes siguen un patrón válido, y otra para asegurarnos de que la fecha de entrega (RequierdDate) es posterior a la fecha actual del pedido (OrderDate). Una vez que hemos definido las clases parciales, estos métodos de validación se ejecutarán cada vez que escribamos código para actualizar nuestros objetos de datos de nuestra aplicación:

VB:

C#:

Añadir un método de ayuda GetCustomer() a nuestro DataContext

Una vez que hemos creado nuestro modelo de clases, y que le hemos añadido reglas de validación, podemos consultar e interactuar con los datos. Podemos hacer esto escribiendo expresiones LINQ sobre nuestro modelo de clases (vimos cómo hacer esto en la tercera parte de esta serie). También podemos mapear SPROCs en nuestro DataContext (esto lo vimos en la sexta parte de la serie).

Cuando creamos una capa de datos con LINQ to SQL normalmente querremos encapsular consultas comunes de LINQ (o SPROCs) en métodos auxiliares que añadiremos a la clase DataContext. Esto lo conseguimos añadiendo una clase parcial a nuestro proyecto. Por ejemplo, podemos añadir un método llamado "GetCustomer()" que nos permita buscar y obtener objetos Customer de la base de datos a partir del valor CustomerID:

VB:

C#:

Paso 2: Usando nuestra capa de datos (seguimos sin SPROCs)

Ya tenemos una capa de datos que encapsula nuestro modelo de datos, integra reglas de validación, y nos permite consultar, actualizar, insertar y borrar datos.

Veamos ahora un escenario simple usándolo para obtener un objeto customer existente, actualizamos el ContactName y el PhoneNumber, y creamos un nuevo objeto Order para asociarlos. El siguiente código hace todo eso en una sola transacción. LINQ to SQL se asegura de que las reglas de validación se cumplen ántes de guardar nada en la base de datos:

VB:

C#:

LINQ to SQL monitoriza todas las modificaciones de los objetos que hemos obtenido de la base de datos, y guarda los objetos que añadimos. Cuando llamamos al método DataContext.SubmitChanges(), LINQ to SQL comprueba las reglas que hemos establecido, y genera automáticamente la SQL que actualizará el registro de Customer e insertará un nuevo registro en la tabla Orders

Un momento - Pensaba que este post iba sobre SPROCs

Si aún estais leyendo, os preguntaréis dónde están los SPROCs en este post. ¿Porque os estoy mostrando el código de arriba que hace que se genere una SQL dinámica? ¿Por qué no os he enseñado cómo llamar a un SPROC para hacer las inserciones/actualizaciones/borrados todavía?

La razón es que el modelo de programación de LINQ to SQL tanto para trabajar con objetos modelados mediante SPROC es exactamente el mismo que con SQL dinámico. La manera en que añadimos validación lógica es exactamente igual (así que todas las reglas que hemos añadido a nuestro modelo de datos se aplicarán también si usamos SPROCs). El código anterior que hemos usado para obtener un cliente, actualizarlo y añadir un nuevo pedido es exactamente igual tanto si usamos SQL dinámico como si usamos SPROCs.

Esta simetría en el modelo de programación es muy potente ya que no tenemos que aprender dos maneras diferentes de hacer las cosas, ni tenemos que decidir al principio del proyecto qué técnica usar, si SPROC o no. Podemos empezar usando el SQL dinámico que nos da LINQ to SQL para las consultas, inserciones, actualizaciones y borrados. Podemos añadir reglas de validación a nuestro modelo. Y luego podemos actualizar el modelo de datos para usar SPROCs - o no. El código y los test que escribamos contra las clases del modelo de datos serán exáctamente iguales.

De ahora en adelante veremos cómo podemos actualizar nuestro modelo de datos usando SPROCs para actualizar/insertar/borrar - mientras seguimos usando las mismas reglas de validación y trabajaremos con los mismos códigos anteriores.

Cómo usar SPROCs en inserciones, actualizaciones y borrados

Podemos modificar la capa de datos que estamos construyendo para que use SPROCs, en lugar de SQL dinámico de dos maneras:

  1. Usando el diseñador de LINQ to SQL para configurar gráficamente la ejecución de los SPROCs en las diferentes operaciones o
  2. Añadir una clase parcial NorthwindDataContext a nuestro proyecto, y entonces implementar los métodos necesarios para la inserción, borrado y actualización. (por ejemplo: InsertOrder, UpdateOrder, DeleteOrder) que serán llamados cuando se realize alguna de las operaciones asociadas. Estos métodos parciales serán pasados a las instancias del modelo de datos que queramos actualizar, y podemos ejecutar tanto SPROC como código SQL para guardarlo.

Cuando usemos la primera aproximación para configurar gráficamente los SPROCs que llamaremos, por debajo se está generando el mismo código (en clases parciales que crea él solo) que escribiríamos si elegimos la segunda opción. En general os recomiendo que uséis el diseñador de LINQ to SQL para configurar los SPROCs en el 90% de los casos - y crear las llamadas personalizadas a procedimientos almacenados en escenarios más avanzados.

Paso 3: Hacer otras inserciones con un SPROC

Empezaremos cambiando nuestro modelo de datos para que use SPROCs con el objeto Order.

Primero nos vamos a la ventana de "Explorador de Servidores" (Server Explorer) de Visual Studio, expandimos el nodo "Stored Procedures" de nuestra base de datos, hacemos clic con el botón derecho y elegimos la opción "Add New Stored Procedure":

Creamos el nuevo procedimiento almacenado que llamaremos "InsertOrder" que añade una nueva fila order a la tabla Orders:

Fijáos que hemos definido el parámetro "OrderId" como un parámetro de salida. ESto es debido a que la columna OrderID es una columna identidad que se autoincrementa cada vez que se añade un nuevo registro. Quien llame a este SPROC deverá pasarle un valor null en ese parámetro - y el SPROC devolverá en ese parámetro el nuevo valor OrderID (llamando a la función SCOPE_IDENTITY() al final del SPROC).

Después de crear el SPROC abrimos el diseñador de LINQ to SQL. De la misma forma que vimos en la sexta parte de esta serie, podemos arrastrar y soltar SPROCs desde la ventana "server explorer" al diseñador. Esto es lo que haremos con el nuevo SPROC que acabamos de crear:

El último paso será decirle a nuestra capa de datos que use el SPROC InsertOrder cuano inserter un nuevo objeto Order en la base de datos. Esto lo hacemos seleccionando la clase "Order" del diseñador LINQ to SQL, y en las propiedades clicamos el botón "..." del método Insert:

Hacemos clic en el botón "..." y aparecerá una ventana que nos permite personalizar las operaciones de inserción:

Fijaos cómo el modo po defecto ("Use Runtime") está configurado para usar LINQ to SQL como generador dinámico de las SQL. Para cambiarlo seleccionamos el radio buton "Customize" y seleccionamos el SPROC InsertOrder de la lista de SPROCS disponibles:

El diseñador de LINQ to SQL calculará una lista de parametros para el SPROC que hemos seleccionado, permitiéndonos mapear las propiedades de nuestra clase Order a los parámetros del SPROC InsertOrder. Por defecto seleccionará el que más se parezca en el nombre. Podemos cambiarlo si queremos.

Una vez que cliquemos en OK está listo. Ahora cada vez que añadamos un nuevo pedido a nuestro DataContext e invoquemos al método SubmitChanges(), se ejecutará el SPROC InsertOrder.

Importante: Aunque estemos usando SPROC para la persistencia, el método parcial "OnValidate()" que creamos (en la primer parte de esta serie) para encapsular las reglas de validación para los pedidos seguirán ejecutándose antes de realizar cualquier cambio. Es decir, tenemos una forma limpia de encapsular la lógica de negocio y las reglas de validación en nuestros modelos de datos, y podemos reutilizarlos tanto si usamos SQL o SPROCS.

Paso 4: Actualizando los clientes con SPROCs.

Ahora vamos a modificar el objeto Customer para manejar las actualizaciones con un SPROC.

Empezamos creando el SPROC "UpdateCustomer":

Fijaos que además de pasar el parámetro @CustomerID, también tenemos un parámetro @Original_CustomerID. La columna CustomerID de la tabla Customers no es un campo autoincremental, y puede modificarse cuando hagamos una actualización. Por tanto necesitamos ser capaces de decirle al SPROC cual es el CustomerID original y el nuevo CustomerID. Vamos a ver cómo mapeamos esto con el diseñador de LINQ to SQL.

Veréis que estamos pasando un parámetro llamado @Version (que es una marca de tiempo) al SPROC. Es una nueva columna que he añadido a la tabla Customers para ayudarme a controlar la concurrencia optimista. Veremos en más detalle este tema en otro post de esta serie - pero en resumen es que LINQ to SQL soporta completamente la concurrencia optimista, y nos permite usar tanto una marca de tiempo o usar valores original/nuevo para detectar si ha habido algún cambio por parte de otro usuario ántes de guardar los datos. Para este ejemplo usaremos una marca de tiempo ya que hace que el código sea mucho más claro.

Una vez que tenemos nuestro SPROC, lo arrastramos y soltamos al diseñador LINQ to SQL para añadirlo como método a nuestro DataContext. Seleccionamos la clase Customer y hacemos clic en el botón "..." de la propiedad Update:

Seleccionamos el radio button "Customize" y seleccionamos el SPROC UpdateCustomer:

Cuando mapeamos las propiedades de los objetos Customer con los parámetros del SPROC, veremos que tenemos que decidir si poner la propiedad "current" en el objeto de datos, o si poner el valor original que estaba en la base de datos antes de obtener el objeto. Por ejemplo, tendremos que asegurarnos de que mapeamos el valor "current" de la propiedad CustomerID en el parámetro @CustomerID, y el valor original en el parámetro @original_customerID.

Cuando hacemos clic en OK ya esta terminado. Ahora cuando actualizemos cualquier cliente y llamemos a SubmitChanges() se ejectuará el SPROC UpdateCustomer en lugar de ejecutarse un SQL dinámico.

Importante: Aunque ahora estemos usando SPROC, el método parcial "OnPhoneChanging()" de la clase Customer (que creamos en el primer post de esta serie) para validar los números de teléfono se seguirá ejecutando de la misma manera ántes de que se guarden los cambios. Tenemos de esta forma una forma limpia de encapsular reglas de negocio y validación a nuestros modelos de datos, y podemos reutilizarlos tanto si usamos SQL dinámico o SPROCs.

Paso 5: Usando el modelo de datos otra vez (esta vez con SPROCs)

Ahora que ya tenemos configurada nuestra capa de datos para usar SPOCs en lugar de SQL dinámico, podemos ejecutar el mismo código que vimos en el paso 2:

Ahora las actualizacion del objeto Customer, y la inserción del objeto ORder, se están ejecutando a través de SPROCs en lugar de SQL dinámico. La lógica de validación que definimos se siguen ejecutando como antes, y el código sigue siendo exactamente el mismo.

Apuntes avanzados cuando usamos SPROCs

Veamos unas cuantas recomendaciones útiles para escenarios con SPROC más avanzados con LINQ to SQL

Uso de parámetros de salida

En casos de inserción (Paso 3) hemos visto cómo podemos devolver el nuevo valor OrderID (que es un valor identidad y autoincremental de la tabla Orders) usando un parámetro de salida en el SPROC. No estamos limitados a devolver sólo valores de columnas identidad con SPROCs y LINQ to SQL - en realidad podemos actualizar y devolver cualquier parámetro. Podemos usarlo tanto para insetar como para actualizar. LINQ to SQL tomará el valor resultado y actualizará la propiedad asociada en el modelo de dato sin que tengamos que hacer ninguna consulta extra para refrescarlo o calcularlo de nuevo.

¿Que pasa si el SPROC da un error?

Si el SPROC da un error mientras inserta, actualiza o borra un dato, LINQ to SQL cancelará y deshará la transacción de todos los cambios asociados a la llamada SubmitChanges(). De manera que nos aseguramos la consistencia de los datos.

¿Podemos escribir código en lugar de usar el diseñador para llamar a un SPROC?

Como ya comenté al principio, podemos usar tanto el diseñador de LINQ to SQL para mapear las operaciones con SPROC o podemos añadir métodos parciales a la clase DataContext programáticamente e invocarlos nosotros mismo. Aquí tenéis un ejemplo del código que deberíamos escribir para sobreescribir el método UpdateCustomer de la clase NorthwindDataContext:

Este código es el que fué generado con el diseñador de LINQ to SQL cuando lo usamos para mapear el SPROC y asociarlo a la operación de Update del objeto Customer. Podemos usarlo como un punto de partida y añadir alguna lógica adicional para hacerlo más personalizado (por ejemplo: usar el valor de retorno del SPROC para lanzar excepciones personalizadas).

Resumen

LINQ to SQL es un ORM muy flexible. Nos permite escribir código limpio orientado a objetos para obtener, acutalizar e insertar datos.

Lo mejor de todo es que nos permite diseñar una capa de datos realmente limpia e independiente de cómo se guardan y cargan los datos de la base de datos. Podemos usar SQL dinámico o SPROCs para esas operaciones. Lo mejor es que el código que use nuestra capa de datos, y todas las reglas de negocio asociadas, serán las mismas sin importar que método de persistencia estemos usando.

En futuros post veremos más conceptos sobre LINQ to SQL como: Herencia simple de tablas, Carga retrasada, concurrencia optimista, y administración de escenarios de N-capas. ESta semana estaré de vacaciones y espero tener más tiempo libre para escribir alguno de ellos.

Espero que sirva

Scott.

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

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

9 pensamientos en “LINQ to SLQ (Parte 7 – Actualizando la base de datos con procedimientos almacenados)

  1. VisualStudio Adicto

    Hola Amigo:

    Que grande que sos, que hermoso blog tan interesante tenes de .NET.
    No te das una idea como he disfrutado con esta entrega de LINQ!!!!!!!
    Sos un grande porque lo explicas detalladamente y facil de enter. Te felicitooooooooo!!!
    Cristian de Cordoba

  2. Richard Almonte

    Hola,

    Que buena explicación das, es el primer post que leo y me animo a leer mas.

    gracias.

  3. Pingback: LINQ | Un Mundo Interesante ....

  4. Pedro

    Fantástico, que bien te explicas, estoy deseando comenzar a usar esto de LINQ to SQL, parece tan intuitivo de usar.

  5. Sarai

    Es muy sorprendente la manera que te tomas tu tiempo para explicar muy preciso y consciso te felicito….!!!!

  6. Sarai

    abusando de eso puedo preguntarte algo…
    tengo un problema al momento de borrar un dato de la base de datos aparentemente me lo borra pero cuando ya lo reviso en la base de datos no esta borrado y cuando intento borrar de nuevo me tira un mega error y es xq no me borra los datos. la conexion la hice con linq to sql server en visual 2008

  7. Vio

    Hola Sarai:

    Gracias por tus comentarios.
    Por lo que comentas en tu segundo comentario, lo único que se me ocurre es que no estés llamando al método Submmitchanges del DataContext después de borrarlo.

    Espero que sirva.
    Un saludo

  8. Jim04

    Muy buen post

    Pero tengo un problema a la hora de actualizar:
    Estoy actualizando usando SPROC lo que pasa es que la primera vez me actualiza perfectamente.
    La segunda vez que intento actualizar me tira una exception de infracción a la primary key, y luego una de no puede agregar una entidad que ya está en uso
    PERO NO ESTOY AGREGANDO NADA NI CAMBIO EL PK

    Ya he perdido mucho tiempo con este error, si me ayudas te lo agradecería muchísimo

Los comentarios están cerrados.