Mapeando Bases de Datos hacia Objetos .NET utilizando LINQ to SQL
Con la llegada de LINQ (Language Integrated Query) tenemos finalmente (entre otras cosas) el tan ansiado ORM (Object Relational Mapping) creado por Microsoft, el cual nos va a permitir no solo convertir transparentemente entidades de base de datos en objetos .NET, sino que nos da la habilidad de hacer todo tipo de queries contra el modelo relacional sin tener nunca que utilizar sentencias SQL, ya que las mismas se generan "tras bastidores".
El ORM al que me refiero se conoce específicamente como LINQ to SQL. Esta es solo una de las variantes de LINQ, puesto que el mismo puede utilizarse con otros orígenes de datos, como por ejemplo XML y objetos. En sí, LINQ brinda el poder de hacer todo tipo de queries en el lenguaje de preferencia, contra cualquier origen de datos, transformar (opcionalmente) los datos a la forma que uno desee y luego manipular fácilmente los resultados.
Quisiera entonces en este post brindar un vistazo rápido sobre cómo puede utilizarse LINQ to SQL para modelar una base de datos relacional utilizando objetos .NET y luego hacer queries sobre los mismos.
Preparando la Aplicación
Para demostrar el beneficio del uso de LINQ to SQL vamos a crear una sencilla aplicación ASP.NET con un DataGridView para poder visualizar los resultados de la consulta. El utilizar ASP.NET como cliente es solo una cuestión de gustos y preferencias, puesto que también podría haber utilizado una aplicación Windows Forms ó incluso una aplicación de Consola para demostrar el concepto.
Abramos entonces Visual Studio 2008 (Beta 2) y creemos una nueva ASP.NET Web Application:
...y a la página Default.aspx agreguémosle el GridView y démosle algún formato agradable:
Generando el Modelo
Necesitamos ahora definir el modelo de objetos con el que vamos a trabajar. Para esto agregamos al proyecto un nuevo elemento del tipo "LINQ to SQL Clases":
En el diseñador que se abre podemos empezar a arrastrar objetos desde el Server Explorer. En este caso, vamos a arrastrar las tablas Customers y Orders de la base de datos Northwind:
Con esto nuestro modelo de clases inicial está listo y podemos pasar a realizar queries sobre los datos.
Realizando Queries
Supongamos ahora que, con el modelo planteado, queremos traer de la base de datos todos los Clientes radicados en Londres y mostrar los resultados en el GridView. ¿Cómo lo haríamos?
Agreguemos el siguiente código al manejador del evento Load de Default.aspx:
Lo primero que estoy haciendo aquí es crear una instancia de la clase NorthwindDataContext, la cual es un intermediario que nos permitirá realizar queries contra las distintas entidades de nuestra base de datos específica, así como aplicar luego los cambios realizados. Esta clase contiene propiedades que representan a cada tabla modelada en el diseñador, así como métodos para cada procedimiento almacenado que hayamos modelado (esto último lo veremos más adelante):
A continuación estoy usando una Query Expression, una de las extensiones al lenguaje agregadas por LINQ. Como pueden ver, se está infiriendo el tipo de datos de retorno para así simplificar el código. Sé que tanto la expresión como la inferencia de tipos suenan y se ven algo chocantes a primera vista, pero son características súper poderosas que veremos de ahora en adelante tanto en C# como en VB. Ahondar en estos temas se sale del alcance de este post, pero para quienes deseen adelantarse un poco pueden echarle un ojo a este post de Edgar Sánchez y a este otro post de Scott Guthrie.
Por último, simplemente tomamos el resultado del query y lo utilizamos como origen de datos del GridView. El resultado es este:
Tenemos entonces a los seis clientes radicados en Londres. ¿Qué pasaría si quisiéramos ahora traer las órdenes de compra asociadas a aquellos clientes?
Trayendo Datos de Varias Tablas
Gracias a los mapeos y relaciones establecidos en nuestro modelo pues no resulta difícil crear ahora un query que traiga todas las órdenes de compra de los clientes radicados en Londres:
Noten cómo puedo navegar fácilmente hacia las relaciones de la clase Customer, en este caso hacia la colección de Ordenes del cliente (cliente.Orders).
Este nuevo query crea también un nuevo objeto con un tipo anónimo (otra de las nuevas características de C# 3.0) con dos cadenas que toman el nombre de las propiedades de los datos originales (OrderID y ContactName). Estos tipos anónimos son sumamente poderosos al usarlos en queries, porque ahorra todo el trabajo de crear clases para que almacenen cada tipo de resultado creado por varios tipos de queries.
Por acá el resultado de este query:
OK, OK, sé lo que están pensando: "Sí, pero yo no quiero SQL dinámico en mi código C#, quiero seguir utilizando mis procedimientos almacenados". LINQ también tiene algo que decir al respecto.
Utilizando Procedimientos Almacenados
El uso de procedimientos almacenados es y seguirá siendo un argumento válido, porque ya sea por razones de rendimiento, de seguridades, de tiempo disponible para migración, de reglas de negocio contenidas en los procedimientos, entre otras, en muchos casos será preferible mantener los procedimientos almacenados que ya hacen el trabajo por nosotros.
Como lo había mencionado antes, podemos agregar procedimientos almacenados a nuestro modelo de la misma forma como hicimos con las tablas. Agreguemos entonces el procedimiento almacenado TenMostExpensiveProducts a nuestro modelo:
Como pueden ver, los procedimientos almacenados se ubican en el panel derecho del diseñador de LINQ to SQL. Ya con este elemento adicional en el modelo, podemos modificar nuestro query para que invoque al procedimiento almacenado y muestre sus resultados en el GridView:
Nótese que aún cuando utilizamos un procedimiento almacenado para traer los datos podríamos a continuación modificar los mismos utilizando las diversas propiedades de los objetos e invocando luego al método SubmitChanges() de NorthwindDataContext para que los cambios sean enviados a la base de datos (esto será motivo de otro post).
El GridView mostrará lo siguiente:
En Resumen
Vaya, este post se hizo un poco más largo de lo que pensaba, pero creo que ha demostrado el punto: LINQ to SQL ofrece una forma realmente simple y fascinante de modelar la capa de datos de una aplicación. Una vez definido el modelo de datos puedes hacer todos los queries que te puedas imaginar con facilidad y eficiencia, sin salir nunca de tu lenguaje de preferencia.
Pronto estaré publicando nuevos posts relacionados a LINQ y sus diversas características. ¡Hay tanto que cubrir!
Julio.