linq to sql 4
Post on 13-Jun-2015
29 Views
Preview:
DESCRIPTION
TRANSCRIPT
LINQ to SQL (4ª Parte) – Actualizando la base de datos
43 respuestas
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:
Parte 1: Introducción a LINQ to SQL
Parte 2: Definiendo el modelo de datos.
Parte 3: Consultando la base de datos
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.
top related