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:

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.

Espero que sirva

Scott

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

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

 Nota del traductor: Voy a estar de vacaciones y no tendré acceso a Internet. Cuando vuelva traduciré lo más rápidamente posible todo lo que Scott nos tenga preparado.