Post on 13-Jun-2015
description
transcript
LINQ to SLQ (Parte 7 – Actualizando la base de datos
con procedimientos almacenados)
9 respuestas
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:
Parte 1: Introducción a LINQ to SQL
Parte 2: Definiendo el modelo de datos.
Parte 3: Consultando la base de datos
Parte 4: Actualizando la base de datos.
Parte 5: Enlazar controles de interfaz de usuario con el ASP:LinqDatSource
Parte 6: Obtener datos con procedimientos almacenados.
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.