usando linq to sql
Post on 06-Dec-2015
244 Views
Preview:
DESCRIPTION
TRANSCRIPT
Usando LINQ to SQL (Introducción)
Gracias a la traduccion de VIO podemos disfrutar de las bondades que LINQ que viene
incluido en el Nuevo Visual Studio puede brindarle al desarrollador…
Hay que recorda que segun Wikipedia Language Integrated Query (LINQ) es un
proyecto de Microsoft que agrega consultas nativas semejantes a las de SQL a los lenguajes
del .NET Framework, inicialmente a los lenguajes Visual Basic .NET y C#. Muchos
conceptos que LINQ ha introducido fueron originalmente probados en Cω, un
proyecto de investigación de Microsoft.
LINQ define operadores de consulta estándar que permiten a lenguajes habilitados con
LINQ filtrar, enumerar y crear proyecciones de varios tipos de colecciones usando la
misma sintaxis. Tales colecciones pueden incluir arreglos, clases enumerables, XML,
conjuntos de datos desde bases de datos relacionales y origenes de datos de terceros. El
proyecto LINQ usa características de la versión 2.0 del .NET Framework, nuevos
ensamblados relacionados con LINQ, y extensiones para los lenguajes C# y Visual Basic
.NET. Microsoft ha distribuido una versión previa del LINQ, consistente de estas
bibliotecas y compiladores para C# 3.0 y Visual Basic 9. Otros lenguajes, como F# y
Nemerle, han anunciado brindar soporte preliminar.
Usando LINQ to SQL (1ª Parte)
En los últimos meses he escrito una serie de post que cubrían algunas de las característcias
que van a venir con Visual Studio y .NET Framework ―Orcas‖. Aquí tenéis los enlaces:
Propiedades automáticas, inicializadores de objectos e inicializadores de
colleciones.
Métodos de extensión.
Expresiones Lambda.
Sintaxis de consultas.
Tipos Anónimos
Las características anteriores hacen que la consulta de datos sea un concepto de primera
clase. Conocemos a este modelo de programación como ―LINQ‖ – que viene de .NET
Language Integrated Query.
Los desarrolladores pueden usar LINQ con cualquier fuente de datos. Pueden expresar
consultas eficientemente en los lenguajes de programación que
eligan, opcionalmente transformar/incrustar los resultados de las consultas en el formato
que quieran, y entonces manipular fácilmente los resultados. Los lenguajes habilitados para
LINQ pueden aportar seguridad de tipos y chequeo en tiempo de compilación el las
expresiones de consulta, y desarrollar herramientas que aporten intelisense, debugging, y un
gran soporte para refactoring cuando escriban código de LINQ.
LINQ soporta un modelo de extensibilidad muy rico que facilita la creación de operadores
eficientes para fuentes de datos. La versión ―Orcas‖ del .NET Framework biene con
librerías que habilitan LINQ sobre objetos, XML y bases de datos.
¿Qué es LINQ to SQL?
LINQ to SQL es una implementación de O/RM(object relational mapping, mapeador de
objetos relacionales) que viene con la versión ―Orcas‖ del .NET Framework, y nos permite
modelar bases de datos relacionales con clases de .NET. Podemos consultar bases de datos
con LINQ, así como actualizar/añadir/borrar datos de ellas.
Modelando bases de datos con LINQ to SQL:
Visual Studio ―Orcas‖ viene con un diseñador de LINQ to SQL que nos aporta una forma
fácil de modelar y visualizar una base de datos como un modelo de objeto de LINQ to SQL.
El próximo post cubrirá en más profundidad cómo usar este diseñador (podéis ver éste
video que hice en Enero para verme construir un modelo LINQ to SQL).
Usando ese diseñador LINQ to SQL puedo crear fácilmente una representación de la base
de datos ―Northwind‖:
El diseño de arriba define cuatro clases: Product, Category, Order y OrderDetail. Las
propiedades de cada clase mapean las columnas de cada table en la base de datos. Cada
instancia de esa clase representa una fila en las tablas.
Las flechas entre las cuatro clases de arriba representan las asociaciones/relaciones entre las
diferentes entidades. Son típicamente modeladas como relaciones primary-key/foreign-key
en la base de datos. La dirección de las flechas en el diseñador indican si la relación es uno-
a-uno o uno-a-varios. Se añadiran propiedades fuertemente tipadas a las entidades
basándose en esto. Por ejemplo, la clase Category de arriba tiene una relación de uno-a-
varios con la clase Product. Esto implica que tendrá una propiedad ―Categories‖ que es una
colección de objetos Product con esa categoría. La clase Product entonces tiene una
propiedad ―Category‖ que apunta a una instancia de la clase Category representando la
categoría a la que pertenece el producto.
El panel de la derecha del diseñador LINQ to SQL contiene una lista de procedimientos
almacenados que interactúan con nuestro modelo de base de datos. En el ejemplo de arriba
hemos añadido un SPROC (Procedimiento almacenado) ―GetProductsByCategory‖. Como
entrada recibe un categoryID, y devuelve una secuencia de Product como resultado.
Veremos cómo llamar a este procedimiento almacenado en un ejemplo.
Entendiendo la clase DataContext
Cuando pulsáis el boton ―save‖ del diseñador de LINQ to SQL, Visual Studio generará
clases .NET para representar las entidades y las relaciones de la base de datos que hemos
modelado. Por cada archivo añadido a nuestra solución por el diseñador LINQ to SQL
también se generará una clase DataContext. Esta clase es a traves de la cual realizaremos
las consultas a las entidades de nuestra base de datos. Esta clase tendrá propiedades que
representarán a cada tabla que hemos modelado, así como métodos para cada
procedimiento almacenado que añadamos.
Por ejemplo, aquí tenéis la clase NorthwindDataContext:
Ejemplos de LINQ to SQL
Una vez que hemos modelado nuestra base de datos con el diseñador de LINQ to SQL,
podemos escribir código fácilmente para trabajar con él. Aquí tenéis unos cuantos ejemplos
que muestran tareas comunes con datos:
1) Consultando Products de la base de datos
El siguiente código usa una consulta LINQ para obtener una secuencia IEnumerable de
objetos Product. Fijáos que este código está consultando a traves de la relación
Product/Category para obtener aquellos productos de la categoría ―Beverages‖.
C#:
VB:
2) Actualizando un producto en la base de datos.
El código siguiente muestra cómo obtener un producto de la base de datos, actualizar su
precio, y guardar los cambios en la base de datos:
C#:
VB:
Nota: VB en ―Orcas‖ Beta1 no soporta Lambdas aún. Pero en la Beta2 sí -de forma que el
código anterior se podrá escribir de una forma más concisa.
3) Añadir una nueva categoría y dos nuevos productos en la base de datos.
El siguiente código muestra cómo crear una nueva categoría, y entonces crear dos nuevos
productos y asociarlos a la nueva categoría. Los tres son después guardados en la base de
datos.
Fijaos como no necesitamos administrar manualmente las relaciones
primarykey/foreignkey. Sólo tenemos que añadir los objetos Product en la colección
―Products‖ de la categoría, y luego añadir el nuevo objeto Category en la colección de
―Categories‖ del DataContext, LINQ to SQL sabrá automáticamente crear las PF/FK
necesarias:
C#:
4)Borar productos de la base de datos.
El código siguiente muestra cómo borrar todos los productos Toy de la base de datos:
C#:
VB:
5) Llamar a un procedimiento almacenado.
El código siguiente muestra cómo obtener entidades de la tabla Product sin usar una
consulta LINQ, sino llamando al procedimiento almacenado ―GetProductsByCategory‖ que
añadimos a nuestro modelo de datos. Fijáos que cuando obtenemos los resultados de la
tabla Product, podemos actualizar/borrarlos y llamar a db.SubmitChanges() para hacer las
modificaciones en la base de datos.
C#:
VB:
6) Obtener productos con paginado del lado del servidor
El código siguiente muestra cómo implementar un paginado eficiente en el lado servidor
como parte de una consulta LINQ. Usando los operadores Skip() y Take(), sólo
devoleremos 10 filas de la base de datos – a partir de la fila 200.
C#:
VB:
Resúmen:
LINQ to SQL nos permite modelar la capa de datos de nuestras aplicaciones de una forma
simple y limpia. Una vez que hayamos definido nuestro modelo de datos, podemos realizar
consultas, inserciones, actualizaciones y borrados sobre ella de forma fácil y eficiente.
Espero que esta introducción os haya abierto el apetito de aprender más. En las próximas
semanas continuaré esta serie de post explorando el LINQ to SQL en más detalle.
LINQ to SQL (2ª Parte – Definiendo nuestras clases del
modelo de datos)
Publicado en .NET, ASP .NET, LINQ, LINQ to SQL, Visual Studio a 12:06 am por
Juanma
En la primera parte de la serie de post sobre LINQ to SQL hablé sobre ―¿qué es LINQ to
SQL?‖ y vimos por encima algunos escenarios que permite.
En aquél post pusimos unos cuantos ejemplos de código donde demostrábamos cómo
mejorar la parte de datos usando LINQ to SQL:
Cómo consultar una base de datos.
Cómo actualizar filas en una base de datos
Cómo añadir y relacionar varias filas en una base de datos.
Cómo eliminar filas de la base de datos.
Cómo llamar a procedimientos almacenados.
Cómo obtener datos con paginación en el servidor.
Mejoramos todos estos escenarios usando un modelo de clases de LINQ to SQL como
éste:
En este segundo post de la serie vamos a ver en más detalle cómo crear el modelo anterior
con LINQ to SQL.
LINQ to SQL, el diseñador de LINQ to SQL, y todas las características que estamos viendo
saldrán con la versión de .NET 3.5 y en la release de Visual Studio ―Orcas‖.
Podéis seguir todos los pasos siguientes descargándo tanto Visual Studio ―Orcas‖ Beta 1
o Visual Web Developer Express ―Orcas‖ Beta 1. Podéis instalar las dos y usarlas sin
ningún problema con Visual Studio 2005.
Crear un nuevo modelo de datos LINQ to SQL
Podemos añadir un modelo de datos de LINQ to SQL a un projecto ASP.NET, Class
Library o Windows, con la nueva opción ―Add New Item‖ seleccionando ―LINQ to SQL‖:
Seleccionando ―LINQ to SQL‖ lanzará el diseñador de LINQ to SQL, y nos permitirá
modelar las clases que representen una base de datos relacional. También creará una clas
fuertemente tipada ―DataContext‖ que tendrá las propiedades que representarán cualquier
tabla que modelemos de la base de datos, así como métodos para cada procedimiento
almacenado que modelemos. Como describimos en la primera parte de esta serie de post, la
clase DataContext es el conducto principal que usaremos tanto para consultar la base de
datos como para guardar los cambios que hagamos.
Aquí tenéis una captura de pantalla de un diseño LINQ to SQL ORM vacío, es lo que veréis
despues de crear un nuevo modelo de datos LINQ to SQL:
Clases Entidad (Entity)
LINQ to SQL nos permite modelar clases que mapeen una base de datos. Estas clases son
típicamente conocidas como ―Clases Entidad‖ (en ingles ―Entity Classes‖) y a las instancias
se las conoce como ―Entidades‖ (en ingles ―Entities‖). Las clases entidad mapean a tablas
de una base de datos. Las propiedades de una clase entidad normalmente mapean las
columnas de la tabla. Cada instancia de una clase entidad representa a una fila de una tabla
de la base de datos.
Las clases entidad definidas por LINQ to SQL no tienen que derivar de una clase base
específica, lo que significa que pueden heredar de cualquier objeto que queramos. Todas las
clases creadas por el diseñador de LINQ to SQL se definen como ―clases parciales‖ – con
lo que podemos, opcionalmente, añadir propiedades adicionales, métodos y eventos.
A diferencia de la característica de DataSet/TableAdapter que aporta VS 2005, cuando
usamos el diseñador de LINQ to SQL no tenemos que especificar qué consultas SQL se
tiene que usar cuando creamos el modelo de datos y la capa de acceso.
En lugar de eso, nos centramos en definir las clases entidad, cómo se mapean con la base de
datos, y las relaciones entre ellas. La implementación del ORM de LINQ to SQL se
encargará de generar la lógica de ejecución SQL por nosotros en tiempo de ejecución para
que podamos interactuar y usar las entitades de datos. Podemos usar sintaxis de consultas
LINQ para indicar cómo consultar nuestro modelo de datos de forma fuertemente tipada.
Crear clases entidad de la base de datos.
Si ya tenemos un esquema de base de datos definido, podemos usarlo para crear clases
entidad LINQ to SQL.
La forma más sencilla de conseguirlo es abrir la base de datos desde el ―Server Explorer‖
de Visual Studio, seleccionar las tablas y vistas (Views) que queramos modelar, y
arrastrarlas al diseñador LINQ to SQL:
Cuando añadimos estas dos tablas (Categories y Products) y una vista (Invoices) de la base
de datos ―Northwind‖ al diseñador de LINQ to SQL, tendremos las siguientes clases
entidad creadas a partir del esquema de la base de datos:
Usando estas clases, podemos ejecutar todos los ejemplos de código (excepto el de
procedimientos almacenados) que vimos en la primera parte de esta serie sobre LINQ to
SQL. No tenemos que añadir ningún código adicional o configuración para habilitar los
escenarios de consulta, inserción, actualización, borrado, y paginación en el servidor.
Nombrado y pluralización
Una de las cosas de las que os daréis cuenta usanto el diseñador de LINQ to SQL es que
automáticamente ―pluraliza‖ los nombres de las tablas y columnas cuando crea las clases
entidad basádas en el esquema de la base de datos. Por ejemplo: la tabla ―Products‖ del
ejemplo se resuelve en una clase ―Product‖, y la tabla ―Categories‖ se resuelve en la clase
―Category‖. Este nombrado de clases hace que vuestro modelo sea más consistente con las
convenciones de nomenclatura de .NET, y encuentro bastante útil que el diseñador haga
esto por mi (especialmente cuando añadimos muchas tablas a nuestro modelo).
Si no os gusta el nombre de una clase o propiedad que el diseñador ha generado, siempre
podréis cambiarlo por el que queráis. Podéis hacerlo editanto el nombre de la
entidad/propiedad en el mismo diseñador o cambiarlo en la rejilla de propiedades:
Esta habilidad de nombrado de entidades/propiedades/asociaciones es muy útil en un gran
número de casos. En particular:
1) Cuando cambie el nombre de una tabla/columna de vuestra base de datos. Como vuestras
entidades tendrán nombres diferentes, podéis decidir actualizar las reglas de mapeado y no
el código de vuestra aplicación o las consultas para usar esas nuevas tablas/columnas.
2) Cuando en el esquema de la base de datos tengais nombres que no son ―limpios‖. Por
ejemplo, en lugar de usar ―au_lname‖ y ―au_fname‖ para los nombres de las propiedades
en una clase entidad, podéis usar los nombres de ―LastName‖ y ―FirstName‖ en vuestras
clases entidad y programar con esos nombres, en vez de cambiarlo en la base de datos.
Relaciones
Cuando arrastremos objetos del ―server explorer‖ al diseñador ―LINQ to SQL‖, Visual
Studio comprobará las relaciones de clave primaria y ajenas de los objetos, y basándose en
ellas creará relaciones por defecto entre las diferentes clases entidad que genere. Por
ejemplo, cuando añadimos las tablas Products y Categories de la base de datos NorthWind
al diseñador LINQ to SQL podemos ver que se ha deducido una relación de uno a n entre
ellas (esto se indica con la felcha del navegador):
Esta relación hará que la clase entidad Product tenga una propiedad llamada ―Category‖
que los desarrolladores usarán para acceder a la entidad Category para un Product dado.
También hará que la clase Category tenga una colección de ―Products‖ que permitirá a los
desarrolladores obtener todos los productos de una Category.
Si no nos gusta cómo el diseñador a nombrado a la relación, siempre podrémos cambiarlo.
Sólo hay que hacer clic en la felcha en el diseñador, ver las propiedades y cambiar el
nombre.
Retrasar la carga
LINQ to SQL permite a los desarrolladores especificar si las propiedades de las entidades
deben precargarse o retrasarse hasta el primer acceso. Podemos personalizar las reglas de
precarga/retraso para las propiedades de las entidades seleccionando cualquier propiedad o
asociación en el diseñador, y en las propiedades poner la propiedad ―Delay Loaded‖ a true
o false.
Por poner un ejemplo, imaginemos la clase entidad ―Category‖ del modelo anterior. La
tabla ―Categories‖ de la base de datos ―NorthWind‖ tiene una columna ―Picture‖ que
contiene una imagen (potencialmente grande) para cada categoría, y sólo queremos esa
imagen cuando vaya a usarla (y no cuando esté haciendo una consulta para obtener los
nombres de las categorías en una lista).
Podríamos configurar la propiedad Picture para que se retrase su carga seleccionandola en
el diseñador de LINQ to SQL y en las propiedades poner ―Delay Loaded‖ a true:
Nota: Además de configurar el significado de la precarga/retraso de las entidades, podemos
sobreescribirlo vía código cuando hagamos consultas LINQ en las clases entidad (lo
veremos en el siguiente post de esta serie).
Usando procedimientos almacenados.
LINQ to SQL nos permite modelar procedimientos almacenados como métodos de nuestra
clase DataContext. Por ejemplo, supongamos que hemos definido un procedimiento
almacenado simple para obtener la información de un producto de un categoryID:
Podemos usar el server explorer de Visual Studio y arrastrar este procedimiento
almacenado al diseñador de LINQ to SQL para obtener un método fuertemente tipado que
invocará a este procedimiento almacenado. Si lo arrastramos encima de la entidad
―Product‖ en el diseñador, el diseñador declarará que el procedimiento almacenado
devuelve un IEnumerable<Product>:
Podemos usar tanto una consulta SQL (que generará una consulta SQL adhoc) o invocar el
procedimiento almacenado añadido para obtener las entidades product de la base de datos:
Usar procedimientos almacenados para actualizar/borrar/insertar datos.
Por defecto LINQ to SQL creará automáticamente expresiones SQL apropiadas para
cuando tengamos que insertar/actualizar/borrar entidades. Por ejemplo, si escribimos el
siguiente código LINQ to SQL para actualizar algunos valores en una instancia de la
entidad ―Product‖:
LINQ to SQL creará y ejecutará una sentencia ―UPDATE‖ apropiada para cuando
aceptemos los cambios (Veremos esto en más profundidad en otros post).
Podemos definir procedimientos almacenados personalizados para INSERT, UPDATE,
DELETE. Para configurar esto, hacemos clic en una entidad del diseñador LINQ to SQL y
en las propiedades de Delete/Insert/Update, en el botón ―…‖, y ponemos un procedimiento
almacenado que ya hayamos definido.
Lo curioso es que el cambio de estas propiedades se está realizando en la capa de mapeo de
LINQ to SQL – lo que implica que la actualización del código que vimos ántes sigue
funcionando sin tener que hacer ninguna modificación. Con esto libramos a los
desarrolladores de que si cambiamos el modelo de datos LINQ to SQL, no tienen que tocar
ningún código para que sigua funcionando si deciden poner un procedimiento almacenado
personalizado.
Resumen
LINQ to SQL provee una forma limpia de modelar las capas de datos de nuestras
aplicaciones. Una vez que tengamos nuestro modelado de datos, podemos realizar de forma
eficiente consultas, inserciones, actualizaciones, y borrados sobre él.
Con el diseñador de LINQ to SQL que viene en Visual Studio y en Visual Web Developer
Express podemos crear y administrar nuestros modelso de datos para LINQ to SQL
extremadamente rápido. El diseñador LINQ to SQL también permite una gran flexibilidad
que nos permite personalizar el comportamiento por defecto y sobreescribir/extender el
sistema para que se adapte a nuestras necesidades.
En próximos post usaremos este modelo que hemos creado para ver en más detalle los
procesos de consulta, inserciones, actualizaciones y borrados. En estos post también
veremos cómo añadir validaciones negocio/datos personalizadas a las entidades que hemos
diseñado.
Mike Taulty tiene una gran cantidad de videos sobre LINQ to SQL aquí, os recomiendo que
los veáis. Así tenéis una forma de aprender viendo cómo se usa LINQ to SQL.
LINQ to SQL (3ª Parte – Consultando la base de datos)
Publicado en .NET, ASP .NET, LINQ, LINQ to SQL, Scott Guthrië a 1:04 pm por Juanma
El mes pasado empezé una serie de post sobre LINQ to SQL. LINQ to SQL es un
framework O/RM (Object relational mapping) que viene como parte del .NET Framework
3.5, que nos permite modelar de forma sencilla bases de datos relacionales con clases de
.NET. Podemos usar, por tanto, expresiones LINQ tanto para consultar a la base de datos
como para actualizar/inertar/borrar datos.
Aquí tenéis los enlaces a los primero dos post de esta serie:
Usando LINQ to SQL (1ª Parte)
LINQ to SQL (2ª Parte – Definiendo nuestras clases del modelo de datos)
En el post de hoy vamos a ver en más detalle cómo usar el modelo de datos que creamos en
la segunda parte, y veremos cómo usarlo para consultar datos en un proyecto ASP.NET.
Modelo de la base de datos Northwind con LINQ to SQL
En el segundo post de la serie vimos cómo crear un modelo de clases LINQ to SQL usando
el diseñador de LINQ to SQL que viene con VS 2008. Aquí tenéis el modelo que creamos a
partir de la base de datos de ejemplo Northwind:
Obteniendo productos.
Una vez que tenemos definido nuestras clases del modelo de datos, podemos consultar y
obtener fácilmente datos de nuestra base de datos. LINQ to SQL nos permite esto usando la
sintáxis de consultas de LINQ sobre la clase NorthwindDataContext que creamos con el
diseñador LINQ to SQL.
Por ejemplo, para obtener e iterar sobre una secuencia de objetos Product podemos escribir
el siguiente código:
En esta consulta hemos usado la sentencia ―where‖ en nuestra consulta LINQ para devolver
aquellos productos de una categoría. Estamos usando el campo/propiedad CategoryID del
producto para hacer el filtro.
Una de las cosas que nos aporta LINQ to SQL es que nos da una total flexibilidad en cómo
consultar nuestros datos, y podemos aprovecharnos de las asociaciones que hicimos cuando
modelamos las clases de LINQ to SQL para hacer consultas más naturales y ricas sobre la
base de datos. Por ejemplo, podemos modificar el filtro de la consulta por el CategoryName
en lugar de por el CategoryID con la siguiente consulta LINQ:
Fijáos en cómo estamos usando la propiedad ―Category‖ de cada objeto Product para
filtrarlos por CategoryName. Esta propiedad fue creada automáticamente por LINQ to SQL
ya que modelamos las clases Category y Product con una relación ―varios a uno‖ en la base
de datos.
Por poner otro ejemplo del uso de las relaciones de nuestro modelo, podríamos escribir la
siguiente consulta LINQ para obtener aquellos productos que tengan más de cinco órdenes
para ellos:
Fijáos cómo usamos la colección ―OrderDetails‖ que LINQ to SQL creó en cada clase
Product (debido a la relación 1 a varios que modelamos en el diseñador LINQ to SQL).
Visualizando consultas LINQ to SQL en el debugger
Los ORM como LINQ to SQL administran automáticamente la creación y la ejecución del
código SQL cuando realizamos consultas o actualizaciones sobre su modelo de objetos.
Una de los mayores preocupaciones que tienen los desarrolladores sobre los ORMs es
―¿pero qué código SQL se está ejecutando?‖ Una de las cosas que hace LINQ to SQL es
poder ver exáctamente qué código SQL se está ejecutando cuando ejecutamos nuestra
aplicación con el debugger.
Con la beta 2 de VS 2008 podemos usar el nuevo plug-in de visualización LINQ to SQL
para ver (y testear) cualquier consulta LINQ to SQL. Simplemente añadimos un breakpoint
y pasamos el ratón por encima y hacemos clic en la lupa para visualizar esa consulta:
ESto nos mostrará un cuadro de diálogo que nos dirá exactamente la SQL que LINQ to
SQL usará cuando se ejecute la consulta para obtener los objetos Product:
Si pulsamos el botón ―Execute‖ de este diálogo nos permitirá evaluar el SQL dentro del
debugger y nos mostrará los resultados de la base de datos:
Obviamente esto hace realmente fácil ver qué lógica de consultas SQL está realizando
LINQ to SQL. Fijáos que podemos sobreescribir la SQL que LINQ to SQL ejecutará si
queremos cambiarlo - sin embargo, en el 98% de los casos creo que os dareis cuenta de que
el código SQL que LINQ to SQL ejecuta es realmente bueno.
Enlazando consultas LINQ to SQL a controles ASP.NET
Los resultados de las consultas LINQ implementa la interfaz IEnumerable – la cual es una
interfaz que los controles de servidor de ASP.NET soportan para enlazar datos. Lo que
implica que podemos enlazar los resultados de cualquier consulta LINQ, LINQ to SQL, o
LINQ to XML a cualquier control ASP.NET.
Por ejemplo, podemos declarar un control <asp:gridview> en una página .aspx de la
siguiente forma:
Luego, podemos enlazar los resultados de la consulta LINQ to SQL que escribimos antes:
Esto generará una página como la siguiente:
Restringiendo los resultados de la consulta.
Hasta ahora, cuando evaluamos una consulta de productos, estamos obteniendo por defecto
todas las columnas de datos necesarias para cubrir la entidad de Product.
Por ejemplo, esta consulta para obtener productos:
El resultado de esta consulta es:
Normalmente sólo queremos un subconjunto de los datos de cada producto. Podemos usar
la nueva característica que LINQ y los compiladores de C# y VB tienen para indicar que
sólo queremos un subconjunto de los datos, modificando la consulta LINQ to SQL de la
siguiente forma:
Con esto obtendremos un subconjunto de los datos que se obtienen de la base de datos
(como vemos con el visor del debugger):
Lo realmente útil de LINQ to SQL es que podemos aprovecharnos de las asociaciones entre
clases de nuestro modelo de datos cuando restringimos los datos. Esto nos permite expresar
consultas útiles y muy eficientes. Por ejemplo, la siguiente consulta obtiene los ID y los
nombres de la entidad Product, el número total de pedidos que hemos hecho de productos,
y los suma al total de pedidos de Productos:
La expresión a la derecha de la propiedad ―Revenue‖ es un ejemplo del uso del método de
extensión ―Sum‖ de LINQ. Toma una expresión Lambda que devuelve el valor de cada
pedido de producto como argumento.
LINQ to SQL es listo y es capaz de transformar la expresión LINQ anterior al siguiente
SQL cuando es evaluado (con el visor del debugger):
La sentencia SQL anterior hace que los valores NumOrders y Revenue se calculen dentro
del servidor SQL, y devuelve los siguientes valores de la base de datos (realmente rápido):
Podemos enlazar el resultado anterior a nuestro gridview:
BTW – en caso de que os lo preguntéis, tenemos intellisense en VS 2008 cuando
escribimos estos tipos de restricciones en las consultas LINQ:
En este ejemplo estamos declarando un tipo anónimo que usa la inicialización de objetos para amoldar y definir la estructura del resultado. Y seguimos teniendo intellisense en VS
2008, chequeo de compilación y soporte para refactoring con estos tipos anonimos:
Paginando los resultados de la consulta.
Una de las necesidades más comunes en entornos web es la posibilidad de hacer
eficientemente la paginanción en las interfaces de usuario. LINQ tiene dos métodos de
extensión que permite hacer esto de forma fácil y eficiente – los métodos Skip() y Take().
Podemos usar los métodos Skip() y Take() para indicar que sólo queremos devolver 10
objetos producto – desde la fila que le pasemos como argumento:
Fijáos que no añadimos ni Skipt() ni Take() en la primera consulta – sino que lo hacemos
después de la consulta (cuando lo enlazamos a la fuente de datos del GridView). Muchos
me preguntan ―¿pero esto no significa que primero obtiene todos los datos de la base de
datos y luego hace la paginación (esto es malo)?‖ No. La cuestión es que LINQ usa un
modelo de ejecución en diferido, es decir, la consulta no se ejecuta hasta que se itera sobre
los resultados.
Uno de los beneficios de este modelo de ejecución en diferido es que nos permite crear
consultas en varias líneas de código (lo que mejora la claridad). También nos permite crear
las consultas después de otras – lo que nos permite composiciones más flexibles y
reutilización.
Una vez que tenemos el método BindProduct(), podemos escribir el siguiente código en
nuestra página para obtener el índice de inicio de la consulta y hacer que los productos sean
paginados y mostrados en el gridview:
Esto nos dará una página de productos, filtrada para mostrar aquellos productos que tengan
más de cinco pedidos, mostrando datos calculados dinámicamente, y que son paginables a
partir de una cadena de consulta:
Nota: Cuando trabajamos contra SQL 2005, LINQ to SQL usará la función SQL
ROW_NUMBER() para crear toda la lógica de paginación en la base de datos. Esto nos
asegura que sólo devolverá las 10 filas de datos que queremos mostrar en la página:
Esto hace realmente fácil y eficiente navegar por grandes cantidades de datos.
Resumen
Hemos visto por encima alguna de las cosas que LINQ to SQL nos ofrece. Para aprender
más sobre expresiones LINQ y las nuevas características de consultas que traen los
compiladores de C# y VB con VS 2008, leed estos post:
Nuevas características de la nueva versión de C# Orcas
Métodos de extensión.
Expresiones Lambda
Sintaxis de consultas
Tipos anónimos
En el próximo post de esta serie sobre LINQ to SQL veremos cómo podemos añadir lógica
de validación a nuestro modelo de clases de datos, y mostraremos cómo podemos usarlo
para encapsular la lógica de negocio que se ejecutará con cada actualización, inserción o
borrado de nuestros datos. Veremos casos más avanzados, cómo usar el nuevo control
<asp:LINQDataSource> para añadir enlaces de datos declarativos a controles ASP.NET,
resolución de errores de concurrencia optimista, y más.
LINQ to SQL (4ª Parte) – Actualizando la base de datos
Publicado en .NET, ASP .NET, LINQ, LINQ to SQL, Scott Guthrië, SQL, Visual Studio a
5:02 pm por Juanma
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.
LINQ to SQL (5ª Parte) – Enlazar controles de interfaz
de usuario con el ASP:LinqDatSource
En las últimas semanas he escrito una serie de post sobre LINQ to SQL. LINQ to SQL es
un O/RM que viene con la versión 3.5 del framework .NET, y nos permite modelar bases
de datos relacionales con clases de .NET. Podemos usar expresiones LINQ para consultar
la base de datos, así como actualizar, insertar y borrar datos.
Aquí tenéis los links a los post anteriores:
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.
En estos post hemos visto cómo podemos usar LINQ to SQL programáticamente para
consultar y actualizar nuestra base de datos.
En el post de hoy veremos el nuevo control <asp:LinqDataSource> de la nueva versión del
.NET Framework (3.5). Este control es una nueva fuente de datos para ASP.NET (como los
controles ObjectDataSource y SQLDataSource de ASP.NET 2.0) que nos va a permitir
enlazar controles de la interfaz de usuario a nuestro modelo de datos LINQ to SQL.
Aplicación de ejemplo que construiremos.
El ejemplo que veremos se trata de una aplicación muy simple que nos va a permitir editar
los datos de la tabla products:
La aplicación le da al usuario las siguientes opciones de gestión:
1. Filtrado de productos por categorías.
2. Ordenar los productos haciendo clic en la cabecera de las columnas (Nombre,
Precio, unidades en stock, etc)
3. Navegar en por los productos de 10 en 10.
4. Edición de los detalles de cualquier producto.
5. Borrar productos de la lista.
La aplicación web la implementaremos con un modelo de datos muy limpio creado con el
ORM LINQ to SQL.
Todas las reglas de negocio y las validaciones lógicas se implementarán en el la capa de
datos – y no en la capa de presentación ni en ninguna página web. Con esto conseguiremos:
1) Un conjunto consistente de reglas de negocio que serán usadas en toda la aplicación, 2)
escribiremos menos código y mejor aún, no lo repetiremos y 3) podremos cambiar las
reglas de negocio cuando queramos sin tener que actualizar ese código en miles de sitios en
toda la aplicación.
También aprovecharemos el soporte de paginado y ordenación de LINQ to SQL para
asegurarnos que tales operaciones no se realizan en la capa intermedia, sino en la base de
datos (es decir, sólo obtendremos 10 productos de la base de datos en cada momento – no
estamos obteniendo miles de filas y ordenándolas/paginándolas en el servidor web).
¿Qué es el control <asp:LinqDataSource> y cómo nos ayuda?
El control <asp:LinqDataSource> es un control de ASP.NET que implementa el patrón
DataSourceControl que se introdujo con ASP.NET 2.0. Es similar a los controles
ObjectDataSource y SqlDataSource en que puede enlazar un control ASP.NET en una
página con la base de datos. La diferencia es que en lugar de enlazar directamente con la
base de datos (como el SqlDataSource) o a una clase genérica (como el ObjectDataSource),
este control está diseñado para enlazar aun modelo de datos con LINQ.
Una de las ventajas de usar este control es que nivela la flexibilidad de los ORMs basados
en LINQ. No tenemos que definir métodos para inserción/consulta/actualización y borrado
para el datasource – sino que añadimos éste control a nuestro modelo de datos, definimos
con qué entidades queremos que trabaje, y enlazamos cualquier control de ASP.NET para
que trabaje con él.
Por ejemplo, para tener un listado básico de los productos que use las entidades Product con
un modelo de datos LINQ to SQL, sólo tenemos que declarar un control
<asp:linqdatasource> en nuestra página que enlaze a la clase datacontext de nuestro LINQ
to SQL, identificar las entidades (por ejemplo: Products). Y ya podemos enlazar un
GridView con él (modificando su propiedad DataSourceID) para obtener un grid como el
siguiente:
Sin tener que hacer nada más, podemos ejecutar la página y tener un listado de los
productos con paginado y ordenación. Podemos añadir los botones edit y delete en el grid y
tener soporte automático para ello. No tenemos que añadir ningún método, mapear ningún
parámetro, ni escribir ningún código para el control <asp:LinqDataSource> para tener todas
estas operaciones. Cuando se hacen actualizaciones, el ORM se asegurará de que todas las
reglas de negocio y de validación que hemos añadido se cumplan ántes de guardar los
datos.
Importante: La belleza de LINQ y LINQ to SQL es que obviamente no sólo sirven para
escenarios como el anterior – o para casos particulares para enlazar controles de interfaces
de usuario como con el LinqDataSource. Como ya hemos visto en los post anteriores,
escribir código con este ORM es muy limpio. Siempre podemos escribir código
personalizado para nuestras interfaces de usuario que trabajen directamente con el modelo
de LINQ to SQL si queremos o cuando nos encontremos en un escenario en el que no se
pueda usar <asp:linqdatasource>
En los siguientes pasos veremos como montar la aplicación que hemos descrito usando
LINQ to SQL y el control <asp:LinqDataSource>
Paso 1: Definir nuestro modelo de datos
Empezaremos definiendo el modelo de datos que usaremos para representar la base de
datos.
Vimos cómo definirlo en la segunda parte de estos artículos. Aquí tenéis una captura de
pantalla de las clases del modelo de datos creado con el diseñador de LINQ to SQL de la
base de datos ―Northwind‖:
Volveremos sobre nuestro modelo de datos en el paso 5 de este tutorial cuando añadamos algunas reglas de validación de negocio. Pero para empezar usaremos el modelo de arriba.
Paso 2: Creación de un listado básico de productos.
Empezaremos a crear la interfaz con una página ASP.NET con un control <asp:gridview>
con algun estilo css:
Podríamos escribir código para enlazar programáticamente nuestro modelo de datos al
GridView (como hicimos en la tercera parte de la serie), o podemos usar el control
<asp:linqdatasource> para enlazar el GridView al modelo de datos.
VS2008 nos permite enlazar nuestro GridView (o cualquier control de servidor ASP.NET)
gráficamente a datos LINQ. Para hacerlo tenemos que pasar a la vista de diseño,
seleccionar el GridView y elegir la opción ―New Data Source …‖ en ―Choose Data
Source‖:
Esto nos mostrará un cuadro de diálogo con una lista de fuentes de datos disponibles.
Seleccionamos la nueva opción ―LINQ‖ y le damos el nombre que queramos:
El diseñador del control <asp:linqdatasource> nos mostrará las clases DataContext de
LINQ to SQL que hay disponibles (incluyendo aquellas que están en las librerías que
tengamos referenciadas):
Seleccionaremos el modelo que creamos con el diseñador de LINQ to SQL.
Seleccionaremos la tabla que queramos que sea la entidad principal con la que enlazar el
grid. En nuestro caso seleccionaremos la clase Products. También seleccionamos el boton
―Advanced‖ y habilitaremos las actualizaciones y borrados para la fuente de datos:
Cuando hagamos clic en el botón ―Finish‖, VS 2008 declarará un contorl
<asp:linqdatasource> en el .aspx y actualizará el gridview para que use esta fuente de datos.
También generará las columnas necesarias para los diferentes campos de la clase Product:
Ahora desplegamos el ―smart task‖ del grid view y le indicamos que queremos habilitar la
paginación, ordenado, edición y borrado:
Podemos pular F5 para ejecutar la aplicación, y veremos una página con el listado de
productos con paginación y ordenado:
También podemos pulsar los botones ―edit‖ o ―delete‖ en cada fila para actualizar los datos:
Si pasamos a la vista de código de la página, veremos las marcas que contiene. El control
<asp:linqdatasource> apunta a la clase DataContext, y a la tabla que le dijimos al principio.
El GridView tiene como fuente de datos el control <asp:linqdatasource> (en el
DatasourceID) y le indica qué columnas tienen que incluirse en el grid, cuál es el texto de la
cabecera, y cual es la expresión de ordenación:
Ahora tenemos lo básico para empezar a trabajar con el comportamiento de esta interfaz de
usuario.
Paso 3: Limpiando las columnas.
El grid tiene muchas columnas, y dos de ellas (el SupplierId y el CategoryID) son claves
ajenas, que no conviene mostrar al usuario.
Eliminando columnas innecesarias
Empezaremos eliminando alguna de las columnas que no necesitamos. Podemos hacer esto
en la vista de código(borrando la declaración de <asp:boundfield> correspondiente) o en la
vista de diseño (haciendo clic en la columna y seleccionando la tarea ―remove‖). Por
ejemplo, podemos eliminar la columna ―QuantityPerUnit‖ y ejectuar la aplicación:
Si hemos usado el contorl <asp:ObjectDataSource> y le hemos indicado explícitamente los
parámetros de actualización (update) a los métodos de actualización (por defecto cuando
usamos un DataSet basado en TableAdapters) una de las cosas que tenemos que hacer es
cambiar la firma de los métodos de actualización del TableAdapter. Por ejemplo: si
eliminamos una columnan del grid, tenemos que modificar el TableAdapter para que
soporte los métodos de actualización sin parámetros.
Con el control <asp:LinqDataSource> es que no tendríamos que hacer este tipo de cambios.
Simplemente borrando (o añadiendo) una columna a la interfaz de usuario y ejecutamos la
aplicación -no hacen falta más cambios. Con esto conseguimos que los cambios que se
hagan en la interfaz de usuario sean mucho más fáciles, y nos permite iteraciones más
rápidas en nuestra aplicación.
Limpiando las columnas SupplierId y CategoryID
Hasta ahora estamos mostrando valores enteros de las claves ajenas en nuestro GridView
para los campos Supplier y Category:
Desde el punto de vista del modelo de datos es correcto, pero no desde el punto de vista del
usuario. Lo que queremos hacer es mostrar el nombre de la categoría y el nombre del
proveedor, y mostrar una lista desplegable en modo edición para que podamos cambiar
estas asociaciones de forma fácil.
Podemos cambiar el gridview para que muestre el nombre del proveedor y el de la
categoría en lugar del ID, cambiando el <asp:BoundField> por un <asp:TemplateField>. Y
en este templateField añadimos el contenido que queramos para personalizar la vista de la
columna.
En el código siguiente aprovecharemos la capacidad de LINQ to SQL, que modeló este
campo como una propiedad. Es decir, posdemos enlazar de forma fácil las propiedades
Supplier.CompanyName y Category.CategoryName al grid:
Ahora ejecutamos la aplicación y tenedremos los valores de Category y Supplier de la
siguiente forma:
Para tener una lista desplegable en estos dos campos en el modo edición, tenemos que
añadir dos controles <asp:LinqDataSource> a la página. Los configuraremos para que se
enlacen a las entidades Categories y Suppliers del modelo de datos creado por LINQ to
SQL:
Ahora volvemos a la columnas <asp:TemplateField> que añadimos antes y
personalizaremos la parte de edición (especificando un EditItemTemplate). De forma que
tendremos una lista desplegable en la vista edición, donde los valores disponibles se
obtendrán de las fuentes de datos de categorías y proveedores, y enlazaremos doblemente el
valor seleccionado al SupplierId y CategoryID de las claves ajenas:
Y ahora, cuando el usuario haga clic en el botón de edición, se mostrará una lista con todos
los proveedores disponibles:
Y cuando salvemos los cambios se guardará el valor seleccionado en la lista desplegable.
Paso 4: Filtrando el listado de productos.
Más que mostrar todos los productos de la base de datos, podemos actualizar la interfaz de
usuario para incluir una lista desplegable que permita al usuario filtrar los productos por
una categoría particular.
Como ya añadimos un <asp:LinqDataSource> enlazada a las Categorías de nuesto modelo
de LINQ to SQL, sólo tendremos que crear un nuevo control de lista desplegable y
enlazarlo. Por ejemplo:
Cuando ejecutemos la página tendremos un filtro con todas las categorías al principio de la
página:
Lo último que queda es que cuando seleccionemos una categoría se muestren los productos
de dicha categoría en el gridview. Lo más facil es seleccionar la opción de ―Configure
DataSource‖ en el smart task del GridView:
Con esto veremos el cuadro de dialogo para configurar el <asp:LinqDataSource> que
usamos al principio del tutorial. Seleccionamos el botón ―Where‖ para añadir un campo de
filtro al datasource. Podemos añadir cualquier número de expresiones, y declarativamente
asignar los valores con los que filtrar (por ejemplo: de una querystring, de valores de
formulario, de cualquier control en la página, etc).
Vamos a poner un filtro de productos por su CategoryID, y obtenemos el valor con el que
filtrar de la lista desplegable que creamos antes:
Cuando le damos a ―Finish‖, el contorl <asp:linqdatasource> se actualizará para reflejar los
cambios de filtrado:
Y cuando ejecutamos la aplicación el usuario será capaz de elegir una categoría en la lista
de filtrado y paginar, ordenar, editar y borrar los productos de una categoría:
El control <asp:LinqDataSource> aplicará las expresiones LINQ necesarias para
que cuando se ejecute contra el modelo de datos LINQ to SQL sólo se obtengan los datos
necesarios de la base de datos (por ejemplo: en el grid anterior sólo obtendremos 3 filas con
los productos de la categoría Confection).
También podemos gestionar nosotros el evento Selecting si queremos escribir expresiones
LINQ personalizadas.
Paso 5: Añadir reglas de validación de negocio
Como ya vimos en la cuarta parte de esta serie de post, cuando definimos modelos de datos
con LINQ to SQL tendremos un conjunto de esquemas de validación por defecto en el
modelo de clases. Es decir, si intentamos meter un valor nulo en una columna requerida,
intentar asignar un string a un entero, o asignar una valor de clave ajena en una fila que no
exista, el modelo lanzará un error y se asegurará de que la integrar de la base de datos se
mantiene.
El esquema básico de validación es sólo el primer paso, y es muy raro en la mayoría de
aplicaciones reales. Normalmente necesitaremos añadir reglas adicionales a nuestro modelo
de datos. Afortunadamente con LINQ to SQL podemos añadir estas reglas de forma muy
fácil (para más detalles sobre este tema, leed la cuarta parte de esta serie de post).
Ejemplo de escenario con reglas de validación
Por ejemplo, supongamos una regla básica que queramos reforzar. Queremos que un
usuario de nuestra aplicación no pueda interrumpir un producto mientras haya unidades
pedidas:
Si un usuario intenta guardar la fila anterior, queremos prevenir que ese cambio se guarde
en la base de datos y que genere un error para que el usuario lo arregle.
Añadiendo una regla de validación al modelo de datos.
El sitio incorrecto para añadir este tipo de validación es en la capa de presentación.
Añadirlo en esta capa implica que esta regla será sólo válida en un lugar concreto, y no será
automático en cualquier parte de la aplicación. Distribuir la lógica de negocio en la capa de
presentación también hará que nuestra vida sea realmente penosa a medida que nuestra
aplicación crezca – ya que cualquier cambio/actualización en nuestro negocio hara
necesarios cambios de codigo en todas partes.
El lugar correcto para este tipo de validación es en las clases del modelo de datos de LINQ
to SQL. Como ya vimos en la cuarta parte de esta serie, todas las clases se generan por el
diseñador de LINQ to SQL como clases parciales – con lo que podemos añadir
métodos/eventos/propiedades fácilmente. El modelo de LINQ to SQL ejectuará los
métodos de validación que podemos implementar.
Por ejemplo, Podemos añadir una clase parcial Product a nuestro proyecto que implemente
el método parcial OnValidate() que LINQ to SQL llama antes de guardar las entidades de
Product. En este método podemos añadir la siguiente regla de negocio para segurarnos que
los productos no pueden tener un Reorder Level si el producto es discontinued:
Una vez que añadimos la clase anterior al proyecto LINQ to SQL, la regla anterior se
comprobará cada vez que alguien use nuestro modelo de datos e intente modificar la base
de datos. Esto se hace tanto para los productos existentes que se vayan a actualizar como
para los que se vayan a añadir nuevos.
Como el <asp:LinqDAtaSource> trabaja contra nuestro modelo de datos, cada
actualización/inserción/borrado pasará por esta regla de validación antes de guardar los
cambios. No necesitamos hacer nada más en la capa de presentación para que se cumpla
esta regla – se comprobará cada vez que se use nuestro modelo de datos.
Añadir un manejador de errores en la capa de presentación.
Por defecto si un usuario usa nuestro GridView para meter una combinación no válida
de UnitOnOrder/Discontinued, nuestro modelo LINQ to SQL lanzará una excepción. El
<asp:LinqDataSource> capturará la excepción y nos proporciona un evento que podemos
usar para controlarlo. Si nadie usa el evento, el contol GridView (u otro) enlazado al
<asp:LinqDataSource> capturará el error y proveerá un evento para controlarlo. Si nadie
controla el error será pasado al manejador d ela página, y si nadie lo controla, se le pasará al
evento Application_Error() en el archivo Global.asax. Los desarrolladores pueden hacer
esto en cualquier paso del camino para añadir la lógica de errores que queramos en la capa
de presentación.
Para la aplicación que estamos viendo, seguramente el mejor lugar para controlar cualquier
error de actualización sea en el vento rowUpdated del gridView. Este evento se ejectuará
cada vez que se actualize en nuestro datasource, y podemos ver los detalles de la excepción
si falla la actualización. Podemos añadir el siguiente código para comprobar si ha ocurrido
un error, y mostrar un error adecuado en caso de que ocurra:
No hemos tenido que añadir ninguna validación lógica en nuestra interfaz de usuario. En
lugar de eso, estamos obteniendo el error que lanzamos en nuestra lógica de negocio y la
estamos usando para mostrar un mensaje adecuado al usuario (Estamos mostrando un error
más genérico).
También le estamos indicando que queramos que el GridView se mantenga en el modo
edición cuando ocurra un error – de forma que podemos evitar que el usuario pierda los
cambios que estaba haciendo, y modificar los valores y darle a ―update‖ otra vez e intentar
guardarlo. Podemos añadir un control <asp:literal> en el ―ErrorMessage‖ en cualquier parte
de la pagina que queramos para controlar donde queremos que se muestre el error:
Y ahora cuando intentemos actualizar un producto con valores erroneos veremos un
mensaje de error que indica cómo arreglarlo:
Lo bueno de esto es que podemos añadir o cambiar las reglas de negocio sin tener que
cambiar nada en la capa de presentación. Las reglas de validación, y sus mensajes
correspondientes, pueden centralizarse en un lugar en concreto del modelo de datos y se
aplicarán en todas partes.
Resumen
El control <asp:LinqDataSource> nos da una forma fácil de enlazar controles de
ASP.NET a nuestro modelo de LINQ to SQL. Permite obtener/actualizar/insertar/borrar
datos del modelo de datos.
En nuestra aplicación hemos usado el ORM LINQ to SQL para crear un modelo limpio,
orientado a objetos. Añadimos tres contorles ASP.NET a la página (un gridView, una lista
desplegable, y un errormessage literal), y hemos añadido tres contorles
<asp:LinqDataSource> para enlazar a Product, Category y Proveedores:
Escribimos 5 líneas de validación lógica y 11 lineas para la gestión de errores.
El resultado final es una aplicación web simple que permite a los usuarios filtrar los
productos por su categoría, ordenar y paginar eficientemente dichos productos, editar los
productos y guardarlos (con nuestra reglas de negocio), y borrar productos del sistema
(también con nuestra lógica de negocio).
En próximos post veremos en profundidad algunos escenarios con concurrencia optimista,
carga a petición, herencia de mapeado de tablas, y el uso de procedimientos almacenados y
SQL personalizados.
La próxima semana tengo pensado empezar otra serie de post sobre el control
<asp:ListView> – es un nuevo control de ASP.NET en la versión .NET 3.5. Nos permite un
contorl total sobre el código generado para escenarios de datos (sin tablas, sin spans, ni
estilos en linea …), también veremos el soporte para paginación, ordenado, edición e
inserciones. Por ejemplo, podemos usarlo en lugar del Grid con un look and feel totalmente
personalizado. Lo mejor de todo, podemos cambiar el código de la página anterior sin tener
que cambiar nada del modelo de datos, la declaración del <asp:linqdatasource>, o el code-
behind del trato de errores.
LINQ to SQL (Parte 6 – Obtener 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:
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
En estos posts vimos cómo usar expresiones LINQ para obtener programáticamente datos
de la base de datos.
En el post de hoy veremos cómo podemos usar los procedimientos almacenados (SPROCs)
y las funciones definidas por el usuario (UDFs) con nuestro modelo LINQ to SQL. El post
de hoy veremos el caso de los SPROCs para consultar y obtener datos de la base de datos.
En el siguiente post de esta serie veremos cómo actualizar/insertar/borrar datos con
SPROCs.
¿SPROC o no SPROC? Esa es la cuestión
La pregunta sobre cuando usar el SQL dinámico generado por un ORM en lugar de
procedimientos almacenados creando una capa de datos es causa de debates muy
acalorados entre desarrolladores, arquitectos y DBAs. Mucha gente más lista que yo ha
escrito sobre esto, así que no me decantaré ni por un lado ni por otro.
LINQ to SQL es muy flexible, y puede usare para crear un modelo de datos cuyos objetos
sean independientes del esquema de la base de datos, y puede encapsular lógica de negocio
y reglas de validación que funcionan tanto si se usa SQL generado dinámicamente o a
través de SPROCs.
En el tercer post de esta serie, hablamos sobre cómo podemos escribir expresiones LINQ
contra el modelo de LINQ to SQL como el siguiente código:
Cuando escribimos expresiones LINQ como esta, LINQ to SQL ejecutará el SQL dinámico
necesario para obtener los objetos de Product que cumplan las restricciones.
Como aprenderemos en este post, también podemos mapear SPROCs en la base de datos
con la clase DataContext generada por LINQ to SQL, que nos permitirá obtener los mismo
objetos de Product llamando a un procedimiento almacenado:
Esta habilidad de poder usar tanto SQL dinámico como SPROCs con una capa de datos
limpia es muy útil y nos permite una gran flexibilidad en nuestros proyectos.
Pasos para mapear y llamar a SPROC con LINQ to SQL
En el segundo post de la serie vimos cómo usar el diseñador LINQ to SQL para crear el
siguiente modelo de clases:
Fijaos en las dos partes del diseñador. La de la izquierda nos permite definir el modelo de
datos que mapeara nuestra base de datos. El de la derecha nos permite mapear SPROCs (y
UDFs) en nuestro objeto DataContext, que podemos usar en lugar del SQL dinámico para
trabajar con los objetos de nuestro modelo de datos.
Cómo mapear un SPROC en un DataContext de LINQ to SQL
Para mapear SPROCs en la clase DataContext, vamos primero al explorador de servidores
de VS 2008 y miramos a los SPROCs de nuestra base de datos:
Haciendo doble clic en cualquier SPROC se abrirá para edición y podremos ver el código.
Por ejemplo, aquí tenéis el SPROC ―CustOrderHist‖ de la base de datos Northwind:
Para mapearlo en nuestra clase DataContext, lo arrastarmos y soltamos desde el explorador
de servidores al diseñador de LINQ to SQL. Automáticamente se creará un nuevo método
en la clase DataContext:
Por defecto el nombre del nuevo método en la clase DataContext será el mismo que el del
SPROC, y el tipo de datos devueltos se creará automáticamente con el siguiente patron:
―[NombredelSPROC]Result‖. Por ejemplo: el SPROC de arriba devolverá una secuencia de
objetos del tipo ―CustOrderHistResult‖. Podemos cambiar el nombre del método
seleccionándolo en el diseñador y cambiarlo en la ventana de propiedades.
Como llamar a un nuevo SPROC mapeado.
Una vez que hemos seguido los pasos para mapear el SPROC en la clase DataContext, es
muy fácil de usar. Todo lo que tenemos que hacer es llamarlo para obtener los resultados
fuertemente tipados:
En VB:
En C#:
Además de poder hacer un bucle sobre los resultados, también podemos enlazar los
resultados con cualquier control para mostrarlos. Por ejemplo, el siguiente código enlaza
los resultados del SPROC a un control <asp:gridview>
Con lo que mostramos la historia de productos de un cliente:
Mapeando los tipos resultado de los SPROC del modelo de datos
En el SPROC ―CustOrderHist‖ devolvía una secuencia de objetos con dos columnas: el
nombre del producto y el numero total de pedidos que el cliente ha hecho de ese producto.
El diseñador LINQ to SQL definió la clase ―CustOrderHistResult‖ para representar los
resultados.
También podemos decidir mapear los resultados del SPROC a una clase de nuestro modelo
de datos (por ejemplo: a una entidad Product o Order).
Por ejemplo, tenemos el SPROC ―GetProductsByCategory‖ en nuestra base de datos que
devuelve la siguiente información:
Como ántes podemos crear un método ―GetProductsByCategory‖ en la clase DataContext
que llama a este SPROC arrastrándolo al diseñador de LINQ to SQL. Más que simplemente
arrastrar el SPROC al diseñador, lo arrastraremos encima de la clase ―Product‖:
Con esto, el método ―GetProductsByCategory‖ devolverá una secuencia de objetos
―Product‖:
LINQ to SQL seguirá los cambios hechos a los objetos que se devuelvan como si fuesen
objetos Products obtenidos a partir de expresiones LINQ. Cuando llamemos al método
―SubmitChanges()‖ todos los cambios hechos a esos objetos se guardarán en la base de
datos.
Por ejemplo, con el siguiente código obtenemos y cambiamos el precio de todos los
productos de una categoría aumentándolo en un 90 %:
Para entender cómo funciona el método SubmitChanges() y el seguimiento que se hace de
los cambios, y ver cómo podemos añadir lógica de negocio a nuestro modelo de datos leed
el cuarto post de esta serie.
En el próximo post de esta serie veremos también cómo cambiar el SQL generado cuando
insertamos/actualizamos/borramos datos con SPROCs personalizados. Lo bueno de todo
esto es que el código anterior no habrá que cambiarlo si hemos configurado la clase
DataContext para que use SPROCs para las actualizaciones -
Manejando resultados múltiples desde SPROCs
Cuando un procedimiento almacenado puede devolver varios tipos de datos, el tipo de
resultado del SPROC en la clase DataContext no puede ser fuertemente tipado. Por
ejemplo, imaginemos el siguiente SPROC que puede devolver un producto o un pedido
dependiendo del parámetro de entrada:
LINQ to SQL permite crear métodos auxiliares para devolver Product o Order añadiendo
una clase parcial ―NorthwindDataContext‖ al proyecto que defina un método (que en este
caso llamaremos ―VariablesShapeSample‖) que invoca al SPROC y devuelve un objeto
IMultipleResult:
VB:
C#:
Una vez que añadimos este método al proyecto podemos llamarlo y convetir los resultados tanto a una secuencia de Product como de Order:
VB:
C#:
Soporte de funciones definidas por el usuario (UDFs)
Además de SPROCS, LINQ to SQL también soporta tanto funciones de usuario de valores
y de tablas de valores (UDFs). Una vez que añadimos un método a la clase DataContext,
podemos usar estas funciones en nuestras consultas LINQ.
Por ejemplo, veamos la función simple ―MyUpperFunction‖:
Podemos arrastrar y soltar desde el explorador de servidores al diseñador de LINQ to SQL
para añadirlo como un método a nuestro DataContext:
Luego podemos usar esta función UDF en expresiones LINQ cuando escribimos consultas
contra nuestro modelo LINQ to SQL:
VB:
C#:
Si usamos el visualizador de debug de LINQ to SQL del que ya hablamos aquí, podemos
ver cómo LINQ to SQL transforma la expresión anterior en una SQL que ejecutará el UDF
en la base de datos en tiempo de ejecución:
Resumen
LINQ to SQL soporta poder usar procedimientos almacenados y UDFs contra la base de
datos y los integra en nuestro modelo de datos. En este post hemos visto cómo podemos
usar SPROCs para obtener datos y pasarlo entre nuestras clases del modelo. En el próximo
post veremos cómo podemos usar SPROCS para sobreescribir la lógica de
actualización/inserción/borrado cuando llamamos a SubmitChanges() en el DataContext
para guardar los cambios.
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:
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.
LINQ to SQL (Parte 8 – Ejecutar consultas
SQL personalizadas)
En las últimas semanas he escrito una serie de post sobre LINQ to SQL. LINQ to SQL es
un ORM que viene con .NET 3.5, y nos permite modelar bases de datos relacionales en
clases. Podemos usar expresiones LINQ para consultar la base de datos y también para
actualizar, insertar y borrar datos.
Aquí teneis los enlaces a los diferentes post de la serie:
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.
Parte 7: Actualizando la base de datos con procedimientos almacenados.
En los dos últimos post vismo cómo podemos usar los procedimientos almacenados de
nuestra base de datos para consultar, insertar, actualizar y borrar datos con el modelo de
LINQ to SQL.
Una pregunta que me han hecho mucho desde que he escrito estos post es: ¿que pasa si
quiero control total sobre las consultas SQL que usa LINQ to SQL – pero no quiero usar
SPROCs para hacerlo? En el post de hoy veremos eso – y veremos cómo podemos usar
expresiones SQL personalizadas para que LINQ to SQL las use en lugar de las que
generaría él.
Uso de expresiones LINQ con LINQ to SQL.
Supongamos que hemos usado el diseñador de LINQ to SQL de VS 2008 para modelar un
conjunto de clases a partir de la base de datos Northwind (esto lo vimos en el segundo post
de la serie):
En el tercer post vimos cómo podemos usar LINQ con las nuevas características de VB y
C# para consultar el modelo de clases y devolver un conjunto de objetos que representan
las filas y columnas de la base de datos.
Por ejemplo, podemos añadir un método a la clase DataContext ―GetProductsByCategory‖
que usa una consulta LINQ para devolver objetos de Products de la base de datos:
VB:
c#:
Una vez definido nuestro método de LINQ, podemos escribir el siguiente código para
obtener productos e iterar sobre ellos:
VB:
Cuando se evalúa la expresión LINQ del método ―GetProductsByCategory‖, el ORM LINQ
to SQL ejectuará un SQL dinámico para obtener los datos de la tabla Product para crear los
objetos Product. Podeis usar el Visualizador de Debug de LINQ to SQL para ver en el
debugger cuál es la expresión LINQ que se ejectuará.
Uso de consultas SQL personalizadas con LINQ to SQL
En el ejemplo de arriba no tenemos que escribir ningún código SQL para consultar y
obtener objetos Product fuertemente tipados. LINQ to SQL traduce la expresión LINQ a
SQL por nosotros.
¿Pero que pasa si queremos un control total sobre el SQL que se está ejecutando en nuestra
base de datos, y no queremos que LINQ to SQL lo haga por nosotros? Una forma de
conseguir esto es usando SPROC como ya vimos en las partes 6 y 7 de esta serie. La otra
forma es usar el método auxiliar ―ExecuteQuery‖ de la clase DataContext y usar una
expresión SQL personalizada que le demos.
Usando el método ExecuteQuery
El método ExecuteQuery toma una expresión SQL como argumento , con un conjunto de
parámetros, y la ejecuta contra la base de datos (incluyendo JOINs personalizados sobre
varias tablas.
Lo que hace que ExecuteQuery sea tan útil es que nos permite especifiar cómo queremos
devolver los valores de la expresión SQL. Podemos hacer esto pasándole un parámetro
tipado al método o usando una versión genérica del método.
Por ejemplo, podemos cambiar el método GetProductsByCategory() que creamos ántes -
con una expresión LINQ- para que use el método ExecuteQuery para ejectuar un SQL que
nosotros le digamos:
VB:
C#:
Ahora podemos llamar al método GetProductsByCategory() de la misma forma que ántes:
De esta manera será nuestra consulta SQL la que se ejecutará contra la base de datos – y no
el SQL dinámico que generaría la expresión LINQ.
SQL personalizado y tracking de objetos para las actualizaciones
Por defecto cuando obtenemos objetos con LINQ to SQL, se hace un tracking sobre los
cambios que les hacemos. Si llamamos al método ―SubmitChanges()‖ guardará los datos de
forma transaccional en la base de datos. Vismo esto en la cuarta parte de esta serie de post.
Una de las característcias del metodo ExecuteQuery() es que participa en este tracking de
objetos para actualizar el modelo. Por ejemplo, podemos escribir el siguiente código para
obtener todos los productos de una categoría y rebajar los precios un 10%:
Como dijimos que el tipo de resultado del ExecuteQuery en el método
GetProductsByCategory fuese ―Product, LINQ to SQL sabe cómo guardar los cambios. Y
cuando llamemos a SubmitChanges los guardará.
SQL personalizado con clases personalizadas.
El método ExecuteQuery nos permite especificar cualquier clase como tipo de resultado de
la consulta SQL. La clase no tiene porqué haberse creado con el diseñador LINQ to SQL,
o implementar ninguna interfaz.
Por ejemplo, definimos la clase ProductSummary con un subconjunto de las propiedades de
Product (fijáos que hemos usado la característica de propiedades automáticas):
Podríamos crear otro método en nuestro DataContext llamado
GetProductSummariesByCategory() que nos devuelva objetos de esa clase. Fijáos cómo la
siguiente SQL obtiene sólo un subconjunto de Product – El método ExecuteQuery() se
encarga de mapea automáticamente las propiedades a objetos de la clase ProductSumary:
Ahora podemos invocar a este método e iterar sobre los resultados con el siguiente codigo:
SQL personalizadas para inserciones, actualizaciones y borrados.
Además de usar SQL personalizadas para consultar datos, también podemos hacerlas para
insertar, actualizar y borrar datos.
Esto lo conseguimos creando los métodos parciales adecuados para cada operacion para la
entidad que queramos cambiar en nuestra clase DataContext. Podemos usar el método
ExecuteCommand del DataContext para escribir el SQL que queramos. Por ejemplo, para
sobreescribir el comportamiento de borrado de la clase Product definimos el siguiente
método parcial:
Y si escribimos un código que elimine un producto de la base de datos, LINQ to SQL
llamará al método DeleteProduct – que ejecutará una SQL personalizada en lugar del SQL
dinámico que LINQ to SQL usaría:
Resumen
El ORM LINQ to SQL genera y ejectua un SQL dinámico para las consultas,
actualizaciones, inserciones y borrados contra la base de datos.
Para escenarios más avanzados, o en caso donde queramos un control total sobre el SQL
que se ejecuta, también podemos personalizar el ORM para que ejecute SPROCs, o
nuestras consultas SQL personalizadas. Esto nos da una gran flexibilidad a la hora de
construir y extender nuestra capa de datos.
En próximos post veremos algunos conceptos de LINQ to SQL como: Herenacia simple de
talbas, carga a petición, concurrencia optimista, y escenarios de N-capas.
LINQ to SQL (Parte 9 – Uso de expresiones LINQ
personalizadas con el control )
En las últimas semanas he escrito una serie de post sobre LINQ to SQL. LINQ to SQL es
un ORM que viene con .NET 3.5, y nos permite modelar bases de datos relacionales en
clases. Podemos usar expresiones LINQ para consultar la base de datos y también para
actualizar, insertar y borrar datos.
Aquí tenéis los enlaces a los diferentes post de la serie:
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.
Parte 7: Actualizando la base de datos con procedimientos almacenados.
Parte 8: Ejecutar consultas SQL personalizadas.
En la quinta parte vimos el control <asp:LinqDataSource> de .NET 3.5 y hablamos sbre
cómo podemos enlazar controles de ASP.NET a LINQ to SQL. También vimos cómo
usarlo con el control <asp:ListView> (El control asp:ListView (Parte 1 – Creación de una
página de listado de productos con una CSS limpia))
En ambos artículos las consultas que hacíamos eran relativamente sencillas (la clausula
where se ejecutaba sobre una tabla simple). En el post de hoy veremos cómo usar toda la
potecia de las consultas de LINQ con el control LinqDataSource, y veremos cómo usar
cualquier expresion LINQ to SQL con él.
Pequeña recapitulación: <asp:LinqDataSource> con una sentencia Where.
En estos dos post vimos cómo usar el filtro del control LinqDatasource para declarar un
filtro en un modelo LINQ to SQL.
Por ejemplo, supongamos que hemos creado un modelo LINQ to SQL de la base de datos
Northwind (que ya vimos en la segunda parte de esta serie), podríamos declarar un control
<asp:LinqDataSource> en la página con un filtro <where> que devuelve aquellos productos
de una categoría dada. (especificada a partir del valor ―categoryid‖).
Luego, podemos enlazar un <asp:gridView> a este datasource y habilitar la paginación,
edición y ordenación.:
Cuando ejecutamos la página anterior tendremos un GridView que soportará
automáticamente la ordenación, paginación y edición sobre el modelo de Produt:
Usando los parámetros del <where> de esta forma funciona muy bien en escenarios típicos.
Pero ¿qué pasa si que el filtrado de Product sea más complejo? Por ejemplo, ¿Si sólo
queremos mostrar los productos suministrados por un conjunto dinámico de paises?
Uso del evento Selecting del <asp:LinqDataSource>
En consultas personalizadas podemos implementar un manejador de eventos para el evento
―Selecting‖ en el control <asp:LinqDataSource>. Con este manejador podemos escribir el
código que queramos para obtener los datos. Esto lo podemos hacer con una expresión
LINQ to SQL, o llamar a un procedimiento almacenado o usar una expresión SQL
personalizada. Una vez que obtenemos la secuencia de datos, todo lo que tenemos que
hacer es asignar la propiedad ―Result‖ al objeto LinqDataSourceSelectEventArgs. El
<asp:LinqDataSource> usará esta secuencia como los datos con los que trabajará.
Por ejemplo, aquí tenéis una consulta LINQ to SQL que obtiene aquellos productos de los
proveedores de un conjunto de países:
VB:
C#:
Nota: No tenemos que escribir la consulta en el código del manejador. Una solución más
limpia sería encapsularla en un método de ayuda al que podríamos llamar desde el propio
manejador. Esto lo vimos en la parte 8 de esta serie (usando el método
GetProductsByCategory).
Ahora, cuando ejecutemos la página usando este manejador, sólo obtendremos los
productos de los proveedores de ciertos países:
Una de las cosas más interesantes es que la paginación y la ordenación siguen funcionando
en nuestro GridView – aunque estemos usando el evento Selecting. Esta lógica de
paginación y ordenación ocurre en la base de datos – es decir, sólo devolvemos los 10
productos de la base de datos que necesitamos para el índice actual del GridView
(supereficiente).
Os estaréis preguntando – ¿cómo es posible que tengamos una paginación y ordenación
eficiente incluso cuando lo hacemos con un evento personalizado?. La razón es que LINQ
usa el modelo de ejecución en diferido – es decir, la consulta no se ejecuta hasta que
intentamos iterar sobre los resultados. Uno de los beneficios de este modelo es que nos
permite componer consultas con otras consultas, y añadirles ―comportamiento‖. Podéis leer
más sobre esto en la tercera parte de la serie LINQ to SQL.
En nuestro evento ―Selecting‖ estamos declarando una consulta LINQ personalizada que
queremos ejecutar y luego se la asignamos a la propiedad ―e.Result‖. Pero aún no la hemos
ejecutado (ya que no hemos iterado sobre los resultados o llamado a los métodos ToArray()
o ToList()). El LINQDataSource es capaz de añadir automáticamente los operadores Skip()
y Take() al aconsulta, así como aplicarle una expresión ―orderby‖ — siendo todos estos
valores calculados automáticamente a partir del índice de página y las preferencias de
ordenación del GridView. Sólo entonces el LINQDataSource ejecuta la expresión LINQ y
obtiene los datos. LINQ to SQL se encarga de que la lógica de ordenación y paginado se
haga en la base de datos – y que sólo se devuelvan 10 filas de productos.
Fijáos cómo podemos seguir usando el GridView para editar y borrar datos, incluso cuando
usamos el evento ―Selecting‖ del LINQDataSource:
El soporte de edicion/borrado funcionará mientras que el evento Selecting asigne la
secuencia de resultados a la propiedad Result y sean objetos entidad (por ejemplo: una
secuencia de Product, Supplier, Category, Order, etc). El LinqDataSource administrará los
casos en el que los controles hagan actualizaciones sobre ellos.
Para leer mas sobre cómo funcionan las actualizaciones con LINQ to SQL, leed la parte
cuatro de esta serie, y luego la parte quinta para ver las Updates en accción.
Realizano proyecciones de consultas personalizadas con el evento Selecting.
Una de las características más poderosas de LINQ es la habilidad de ―formar‖ o ―proyectar‖
datos. Podemos hacer esto en una expresión LINQ to SQL para indicar que queremos
obtener sólo un subconjunto de valores de una entidad, y/o calcular nuevos valores
dinámicamente al vuelo con expresiones personalizadas que definamos. Para leer más sobre
esto leed la tercera parte de la serie.
Por ejemplo, podemos modificar el evento ―Selecting‖ para calcular un GridView para que
muestre un subconjunto de información de Product. En el grid queremo mostrar el
ProductID, ProductName, Product UnitPrice, el número de pedidos de ese producto, y el
total de pedidos de ese producto. Podemos calcular estos dos últimos campos con la
siguiente expresión LINQ:
VB:
C#:
Nota: El método Sum para calcular el Revenue es un ejemplo de un método de extensión.
La función es una expresión lambda. El tipo de resultados creados de la consulta LINQ es
un tipo anónimo - ya que el tipo es inferido de la consulta. Métodos de extensión,
expresiones Lambda, y los tipos anónimos son nuevas características de VB y C# en VS
2008.
El resultado de esta expresión LINQ cuando lo enlazamos al GridView es el siguiente:
Fijaos que la paginación y la ordenación sigue funcionando en el GridView – aunque
estemos usando una proyección de LINQ para los datos.
Una característica que no funcionará con las proyecciones es el soporte para la edición.
Esto es debido a que estamos haciendo una proyección personalizada en el método
Selecting, y el LINQDataSource no tiene forma de saber cómo actualizar la entidad. Si
queremos añadir soporte para la edición en este caso, tendremos que crear un control
ObjectDataSource (al que le pondremos un método Update personalizado para
contorlarlos), o hacer que el usuario navegue a una nueva página para hacer la actualización
– y mostrar un DetailsView o FormView enlazado a la entidad Producto para la edición (y
no intentar hacerlo en el grid).
Resumen
Podemos realizar consultas personalizadas sobre el modelo LINQ to SQL usando el soporte
integrado de filtrado del LINQDataSource.
Para habilitar opiciones de filtrado más avanzadas, usaremos el método Selecting del
LINQDataSource. Esto no permitirá crear la lógica que queramos para obtener y filtrar
datos LINQ to SQL. Podemos llamar a métodos para obtener los datos, usar Expresiones
LINQ, llamar a procedimientos almacenados o invocar una expresión SQL personalizada
para hacer esto.
top related