martes, 21 de mayo de 2013

EntityFramework en WCF



La entrada de hoy va a romper un poco la secuencia que venía publicando abordando temas de programación en SQL Server (específicamente de XML), pero hoy que estaba preparando mi curso 70-513 encontré un tema sumamente interesante, al menos a mí me lo parece porque me trabajo encontrar la solución en su momento.

Comencemos por establecer que tendremos 4 proyectos:
  1. Modelo de EntityFramework de una base de datos.
  2. Biblioteca de clases donde se encuentra definido e implementado nuestro servicio WCF.
  3. Aplicación de consola que aloja y expone nuestro servicio WCF.
  4. Aplicación de consola que utiliza el servicio expuesto por la aplicación de consola.
He decidido crear una biblioteca de clases para implementar el servicio para que la implementación sea totalmente independiente del alojamiento de la misma, es decir que esa misma biblioteca de clases la podríamos llevar a WAS, a un servicio de windows, a una aplicación de windows, etc.

Para obtener el script que genera la base de datos basta con dar clic en el siguiente enlace: script para crear base de datos de prueba.

Si le echamos un ojo al script veremos que la tabla Categorias tiene una referencia circular para generar una estructura jerárquica y que la tabla de ProductosRelacionados es básicamente una tabla intersección entre Productos y Productos para establecer los productos relacionados.

Esta entrada primero la voy a llevar por el camino que uno normalmente llevaría para enviar los objetos del EF en un servicio WCF y posteriormente haremos las modificaciones necesarias para corregir el problema que encontraremos.

Vamos a crear una solución en blanco y la llamaremos EFWCF, es importante anotar que tenemos que ejecutar el Visual Studio 2010 como administrador.    Esto es para evitar el uso necesario de netsh para asignar un puerto en particular a nuestra host del servicio WCF.

Vamos a agregar un proyecto de tipo Class Library a nuestra solución y la llamaremos EFWCFDB:
  1. Borramos el archivo Class1.cs que agrega por defecto el template del proyecto.
  2. Vamos a agregar un nuevo elemento de tipo ADO.NET Entity Data Model y lo llamaremos EFWCF.
  3. Elegimos crear el modelo desde una base de datos.
  4. Creamos una nueva conexión que apunte hacia nuestra base de datos llamada EFWCF, utilizaremos autenticación de Windows para efectos de prueba.
  5. Damos clic en Next, elegimos las tres tablas de la base de datos y activamos la casilla de verificación que dice "Pluralize or singularize generated object names" y damos clic en Finish.
El IDE nos presentará un modelo como el que muestro en la siguiente imagen:


Vamos a modificarlo un poco para darle más significado a las entidades y realizaremos los siguientes ajustes:
  1. Renombrar la propiedad Categoria.Categorias1 por CategoriasHijas.
  2. Renombrar la propiedad Categoria.Categoria1 por CategoriaPadre.
  3. Renombrar la propiedad Producto.Productos1 por ProductosRelacionados.
  4. Eliminar la propiedad Producto.Productos dado que su contenido ya está cubierto por la propiedad que acabamos de renombrar.
Con estos cambios nuestro modelo deberá quedar de la siguiente manera:


Con esto podemos dar por terminado nuestro modelo (por el momento) y ahora vamos a crear nuestro proyecto que tendrá la implementación del servicio WCF.

Vamos a agregar un nuevo proyecto a nuestra solución de tipo Class Library y lo llamaremos EFWCFServices:
  • Eliminamos el archivo Class1.cs que trae por defecto la plantilla de este tipo de proyectos.
  • Agregamos un nuevo elemento de tipo WCF Service y lo llamamos EFWCFService.
  • Abrimos el archivo IEFWCFService.cs y eliminamos el procedimiento DoWork con todo y su atributo OperationContract.
  • Abrimos el archivo EFWCFService.cs y eliminamos el procedimiento DoWork.
  • Agregamos referencia al proyecto EFWCFDB.
  • Agregamos referencia a System.Data.Entity.
  • Vamos a agregar una operación a IEFWCFService.cs con la siguiente firma: List<EFWCFDB.Producto> ProductosPorCategoria(int idCategoria)
  • Ajustamos nuestra implementación (EFWCFService.cs) de manera tal que quede así el código:
public class EFWCFService : IEFWCFService
{
    public List<EFWCFDB.Producto> ProductosPorCategoria(int idCategoria)
    {
        using (EFWCFDB.EFWCFEntities db = new EFWCFDB.EFWCFEntities())
        {
            return db.Productos.Where(p => p.Id_Categoria == idCategoria).ToList();
        }
    }
}

Ahora vamos a crear el proyecto que se utilizará como host de nuestro servicio, agreguemos a la solución un proyecto de tipo Console Application y lo llamaremos EFWCFHost.
  1. Vamos a agregar referencia a System.ServiceModel y a nuestro proyecto EFWCFServices.
  2. Agregamos un nuevo elemento de tipo Application Configuration File y lo dejamos con el nombre que trae por defecto.
  3. Vamos a registrar nuestro servicio desde el archivo app.config con el código que he colocado en esta liga: app.config para EFWCFService.    Es importante hacer un ajuste, en system.serviceModel - services - service - host - baseAddresses - add se hace referencia a una dirección que apunta hacia mi computadora, en tu caso deberás modificar esa dirección para que haga referencia al nombre de tu equipo.
  4. Una vez registrado el servicio en el app.config vamos a crear nuestro host en el procedimiento main del archivo Program.cs de nuestro proyecto con el siguiente código: código para host de EFWCFService.
  5. Hay que ejecutar el proyecto EFWCFHost sin depuración, esto es para dejarlo en ejecución y posteriormente agregar una referencia al servicio alojado en él.    Entonces, lo ejecutamos con Ctrl + F5 y lo dejamos corriendo, deberá aparecer una pantalla como esta:

Finalmente vamos a agregar nuestro proyecto cliente el cual será de tipo Console Application y lo llamaremos EFWCFClient.
  1. Agregamos referencia a System.Data.Entity.
  2. Damos clic derecho sobre el proyecto en el Solution Explorer y elegimos Add Service Reference.
  3. En la pantalla que sale vamos a apuntar la dirección base que se configuró en el archivo app.config de la aplicación EWCFHost, en mi caso es http://JorgeToriz-PC:8001/EFWCFServices.
  4. Al dar clic en el botón Go aparecerá nuestro servicio, damos clic en él y le damos el namespace EFWCFServices.
  5. En el procedimiento main de nuestro Program.cs vamos a colocar el siguiente código para mandar a llamar a nuestro servicio
EFWCFServices.EFWCFServiceClient client = new EFWCFServices.EFWCFServiceClient();
Console.WriteLine("Categoría 2: " + client.ProductosPorCategoria(2).Length.ToString());
En la base de datos hay tres productos en la categoría 2, pero está provocando un error debido a las referencias circulares que tenemos en nuestro modelo en el proyecto EFWCFDB.

El mensaje de error es como este: "An error occurred while receiving the HTTP response to http://jorgetoriz-pc:8001/EFWCFServices/v1/EFWCFService.svc. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down)".

¿La solución?, deshabilitar la propiedad "Lazy Loading Enabled" de nuestro modelo, vamos a abrir el archivo EFWCF.edmx que está en el proyecto EFWCFDB, una vez abierto damos clic sobre el fondo, vamos a las propiedades y cambiamos la propiedad de "true" a "false".

Guardamos cambios y compilamos el proyecto EFWCFServices (que mandará a compilar el EFWCFDB por tener referencia en la solución), detenemos nuestro servicio (EFWCFHost) y lo volvemos a arrancar, al ejecutar el cliente el error habrá desaparecido y mostrará algo como esto:


Si queremos que en la respuesta del servicio se incluya la referencia circular, lo deberemos implementar con la función Include de la siguiente manera (proyecto EFWCFServices - EFWCFServices.cs):

return db.Productos.Include("ProductosRelacionados").Where(p => p.Id_Categoria == idCategoria).ToList();

Si depuramos nuestro cliente veremos que ya aparecen los productos relacionados (propiedad ProductosRelacionados) con los productos que formaron parte del resultado, estos datos estarán vacíos por haber deshabilitado la propiedad "Lazy Loading Enabled" del modelo.

Espero te haya resultado de utilidad esta entrada.

No hay comentarios.:

Publicar un comentario