2.15.2011

El Entity Framework en una Arquitectura n-Layer – Parte 5 {Creando la capa de Entidades}

 

Siguiendo con la serie de post respecto al EntityFramework y las arquitecturas n-layer, en este post vamos a trabajar en la creación de una capa de entidades de negocio de forma personalizada y que trabaje ligada con el EF para aprovechar sus capacidades.

En un post anterior, se mostró como trabajar una arquitectura n-layer con el entity framework; sin embargo, muchos de los lectores hicieron notar que con el enfoque actual, debíamos agregar una referencia a la capa de acceso a datos donde tenemos generado nuestros artefactos del entity framework {las entidades de negocio}, y argumentaban que se perdía la separación de capas con este enfoque. Pese a que aún en artículos de la revista del msdn magazine se indicaba que esa era la forma de hacerlo, me puse a buscar una solución para el problema y lo que se publica en este post es una forma de solucionar el problema mencionado.

Capa de Entidades: Es Necesaria?

La capa de entidades de negocio representa el estado de nuestros objetos de negocio. En esta capa se representa el estado de nuestras entidades; el comportamiento esta en la capa de lógica de negocios. ¿Y por qué de esta manera? Simple, porque cuando tengo que transportar los objetos a través de servicios solo debo transportar lo necesario, y en este caso es el estado del objeto.

Las Entidades de negocio tienen como fin transportar los datos desde el repositorio de datos hasta la capa de presentación por lo que están referenciadas en todas las demás capas. Esta capa puede ser representada por DataSets – no recomendado y establecido aquí por qué – o por POCOS, también por las entidades generadas por el entity framework. El caso con el que vamos a trabajar esta vez es con los POCOS, los cuales son muy eficientes a la hora de trabajar en arquitecturas n-layer.¿ Y por qué no utilizamos las entidades generadas por el entity framework? bueno, como dijimos anteriormente estas clases tienen una serie de instrucciones las cuales en muchos de los escenarios en donde trabajamos no las necesitamos – o no nos damos cuentan que existen y sobrevivimos –; además, para poder hacer uso de las entidades generadas por el EF, requerimos agregar la capa donde esta generado nuestro esquema a través del EF, con lo cual rompemos la independencia de las capas, puesto que requerimos agregar el DAL en el UI para poder crear instancias de las clases.

Ejemplo: Utilizando POCOs en el Entity Framework

Para utilizar POCOs con el EF, primero vamos a crear un proyecto de prueba para interactuar con solamente una tabla, la base de datos de prueba tiene el siguiente esquema.

image

Esta tabla queda mapeada de la siguiente forma utilizando el generador del Entity Framework.

image

Este esquema generado queda en el proyecto de la capa de acceso a datos.

image

Luego creamos un proyecto de Entidades de Negocio y creamos en el mismo una clase POCO llamada Producto, además agregamos una referencia a los siguientes dlls.

image

La clase producto va a representar a la entidad producto en nuestro proyecto, y para que podamos aprovechar todas las bondades del EF a la hora de interactuar con la base de datos debemos nombrar esta clase igual que la entidad. Además, debemos agregar una serie de atributos sobre el namespace, la clase y las propiedades de la clase creada para que el Entity Framework entienda el metadata de la clase y pueda tratarla como la entidad que corresponde. Además debemos agregar los namespaces resaltados

 

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. using System.Data;
  7. using System.Data.Objects.DataClasses;
  8. using System.Data.Metadata.Edm;
  9.  
  10.  
  11. [assembly: EdmSchemaAttribute()]
  12. namespace CustomClassEF.EL
  13. {
  14.     [EdmEntityTypeAttribute(NamespaceName = "Blog_SamplesModel", Name = "Producto")]
  15.     public class Producto : EntityObject
  16.     {
  17.         Int64 m_Id;
  18.         string m_Nombre;
  19.         decimal m_Precio;
  20.  
  21.         [EdmScalarPropertyAttribute( EntityKeyProperty=true, IsNullable=false)]
  22.         public Int64 Id
  23.         {
  24.             get
  25.             { return m_Id;}
  26.             set
  27.             {
  28.                 ReportPropertyChanging("Id");
  29.                 m_Id = value;
  30.                 ReportPropertyChanged("Id");
  31.             }
  32.         }
  33.  
  34.         [EdmScalarPropertyAttribute( IsNullable = false)]
  35.         public string Nombre
  36.         {
  37.             get
  38.             {
  39.                 return m_Nombre;
  40.             }
  41.             set
  42.             {
  43.                 ReportPropertyChanging("Nombre");
  44.                 m_Nombre = value;
  45.                 ReportPropertyChanged("Nombre");
  46.             }
  47.         }
  48.  
  49.         [EdmScalarPropertyAttribute(IsNullable = false)]
  50.         public decimal Precio
  51.         {
  52.             get
  53.             {
  54.                 return m_Precio;
  55.             }
  56.             set
  57.             {
  58.                 ReportPropertyChanging("Precio");
  59.                 m_Precio = value;
  60.                 ReportPropertyChanged("Precio");
  61.             }
  62.         }
  63.  
  64.         public Producto()
  65.         { }
  66.  
  67.     }
  68. }

El primer atributo EdmSchemaAttribute indica que el assembly tiene clases que están mapeadas a entidades generadas por el Entity Framework en el modelo conceptual. El atributo EdmEntityTypeAttribute indica que la clase siguiente representa un tipo de una entidad. Seguidamente ponemos nuestra clase producto a heredar de la clase EntityObject, con lo cual tenemos todas las funcionalidades de tracking e interacción con la base de datos a través del EF. Luego utilizamos el atributo EdmScalarPropertyAttribute para indicar que la propiedad representa un tipo escalar; en el caso de la llave primaria se le indica a através del parámetro EntityKeyProperty que la propiedad indicada es la llave primaria, además con el parámetro IsNullable se le indica si el tipo a usar soporta nulos o no en la base de datos. Por último procedemos a reportar el inicio y finalización del cambio del valor de cada una de las propiedades en el set de la misma. Con esto ya tenemos nuestra clase configurada para interactuar con el contexto generado por el EntityFramework y nuestra nueva clase puede reemplazar sin ningún problema a la entidad producto.

Luego procedemos a crear el proyecto de lógica de negocios en donde además de la clase ProductoBL, procedemos a agregar las siguientes referencias:

image

El código de la clase ProductoBL es el siguiente:

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using CustomClassEF.DAL;
  6. using CustomClassEF.EL;
  7.  
  8. namespace CustomClassEF.BL
  9. {
  10.  
  11.     public class ProductoBL
  12.     {
  13.         DAL.Blog_SamplesEntities m_Context = new Blog_SamplesEntities();
  14.  
  15.         public DAL.Blog_SamplesEntities Context
  16.         {
  17.             get { return m_Context; }
  18.             set { m_Context = value; }
  19.         }
  20.  
  21.         public int AgregarProducto(EL.Producto _producto)
  22.         {
  23.             Context.AddObject("Producto", _producto);
  24.             return Context.SaveChanges();
  25.         }
  26.  
  27.  
  28.     }
  29. }

Como podemos ver en el método AgregarProducto, estamos utilizando un parámetro de tipo EL.Producto y no del tipo DAL.Producto. Esto nos indica que estamos esperando una instancia del tipo del POCO y no del tipo de la entidad generada. Además, con el método AddObject procedemos a agregar el POCO y no a convertir el POCO en una entidad.

Luego en la capa de presentación creamos el siguiente proyecto:

image

En nuestro caso creamos una aplicación WPF y como se puede ver en la figura anterior solo hacemos referencia al BL y al EL. Además agregamos un app.config para el manejo de la conexión. La pantalla creada en este proyecto es la siguiente:

image

El código del botón Ok para proceder a salvar el producto creado es el siguiente:

Code Snippet
  1.  
  2. using CustomClassEF.EL;
  3. using CustomClassEF.BL;
  4.  
  5. namespace CustomClassEF.UI
  6. {
  7.     /// <summary>
  8.     /// Interaction logic for MainWindow.xaml
  9.     /// </summary>
  10.     public partial class MainWindow : Window
  11.     {
  12.         public MainWindow()
  13.         {
  14.             InitializeComponent();
  15.         }
  16.  
  17.         private void btnGuardar_Click(object sender, RoutedEventArgs e)
  18.         {
  19.             Producto _producto = new Producto
  20.             {
  21.                 Nombre = txtNombre.Text,
  22.                 Precio = Convert.ToDecimal(txtPrecio.Text)
  23.             };
  24.             ProductoBL _productoBL = new ProductoBL();
  25.             _productoBL.AgregarProducto(_producto);
  26.         }
  27.     }
  28. }

Como podemos ver no agregamos ninguna referencia al DAL, y el comportamiento de la aplicación no sufre ningún cambio, ya que el POCO recién creado funciona perfectamente con el Entity Framework. Nótese que no hemos creado ninguna relación entre entidades todavía – algo que espero hacer en post posteriores – ya que para esto se deben agregar más atributos a los POCOs para hacer que las relaciones y la carga de datos funcione correctamente.

6 comentarios:

chris dijo...

Dentro de un escenario SOA, la capa de entidades se debe replicar a nivel de servicios y a nivel de frontend para hacer la serializacion de los datos?

Diego Rojas dijo...

Cuando se agrega un ESB, el ESB tiene una forma neutral de representar los datos, normalmente XML, por lo tanto las entidades se extienden hasta la capa de servicios, de ahi en adelante es responsabilidad del ESB representar bien el mensaje. En cuanto al UI, si la UI consume servicios entonces cuando hacer referencia al servicio, el proxy le genera las clases correspondientes en el cliente, que deberían ser equivalentes a las que se usan en la capa de servicios, de ahi la importancia de NO utilizar Datasets porque se genera demasiada basura :)

Xavier Dávila dijo...

Amigo muy bueno tu post, tengo una consulta, en el caso de obtener datos de la base de datos, como se realizaría la conversión de datos para asignar en el objeto DTO lo que viene en el objeto del Entity Framework.

Muchas Gracias

Diego Rojas dijo...

Hola Xavier, gracias por leer el blog.
Cuando mapeas pocos al EF de la forma que explica el post, los datos son cargados por el EF en los POCOS

Xavier Dávila dijo...

Tal vez no comprenda mucho porque no tengo mucha experiencia con Entity Framework, pero te comento, en mi EF tengo mapeado un Stored Procedure que trae un listado de catálogos por un codigo:

var items = context.GetItemsCatalogByHeaderCode(catalogCode).AsEnumerable();

esto me devuelve una colección de items de tipo ITEM_CATALOGO proveniente de EF.

Ahora, como estaba en el post, yo cree mi pocos tambien llamado ITEM_CATALOGO pero luego de obtener el la colección no se como convertir o hacer que los datos del EF se carguen en el pocos.

Xavier Dávila dijo...

Saludos Diego,

Sabes que estuve haciendo un ejercicio parecido al que tienes nuevamente, de igual manera con stored procedures, ahora con una tabla parámetro y un SP que trae un parámetro dado un código.

cuando agrego el objeto pocos al contexto no tengo ningun error, sin embargo cuando llego a ejecutar el SP tengo el siguiente error:

An EdmType cannot be mapped to CLR classes multiple times. The EdmType 'MC_AdministrationModel.PARAMETRO' is mapped more than once.

Este es el código que ejecuto en la BL.

PARAMETRO test = new PARAMETRO();
Context.AddObject("PARAMETRO", test);

var param = Context.GetParameterByCode("FOR_GFECHA");