Mostrando entradas con la etiqueta Entity Framework. Mostrar todas las entradas
Mostrando entradas con la etiqueta Entity Framework. Mostrar todas las entradas

miércoles, 5 de noviembre de 2014

[Entity Framework] Code First - Cargando Entidades Relacionadas

 

Las asociaciones pueden trabajarse por lazy load o definir explícitamente que cargue una propiedad relacionada, esos temas serán tratados en el video, así como se comportan las entidades cuando están fuera del contexto.

También veremos como analizar las queries que EF genera, utilizando Sql Server Profiler.

 

 

Link


image

martes, 4 de noviembre de 2014

[Entity Framework] Code First - Manejando Relaciones

 

Cuando se trabaja con entidades es muy raro que se encuentren aisladas por lo general estas se asocian con otras para colaborar.

Las asociaciones que trato en el video serán: uno a uno, uno a muchos o muchos a muchos.

 

 

Link


image

lunes, 3 de noviembre de 2014

[Entity Framework] Code First - Consultando entidades

 

Después de configurar el modelo será necesario aprender a operar con el mismo para obtener la información que nuestra aplicación necesita.

Las queries linq que definamos serán transformadas a consultas sql dinámicamente por EF, por eso es importante entender como funcionan, aquí verán como trabajar entidades simple, como agruparlas, herencia, tipos dinámicos, etc.

 

 

Link


image

domingo, 2 de noviembre de 2014

[Entity Framework] Code First - Fluent Api

 

En el video explico como por medio de código se define el mapping de las entidades pudiendo especificar: clave principal, tipos y precisión en los datos, relaciones entre entidades, herencia, etc.

Por medio de código podrán redefinir las relaciones cuando estas no siguen las convenciones.

 

 

Link


image

sábado, 1 de noviembre de 2014

[Entity Framework] Code First - Convenciones

 

Les comparto este video donde explico las convenciones que Entity Framework utiliza para descubrir los tipos de datos, la clave principal, las relaciones entre entidades, etc.

Podemos definir nuestro modelo sin necesidad de escribir explícitamente en código como mapea con la base de datos, solo será necesario seguir ciertas normas preestablecidas en las convenciones.

 

 

 

Link


image

lunes, 9 de septiembre de 2013

[Entity Framework][Code First] Dividir Tabla (Table Splitting)

 

Introducción


Cuando se definen entidades estas pueden contener atributos que no siempre se quiere recuperar, ya que de hacerlo podría afectar la performance. Un ejemplo se observa en aquellos atributos donde se persisten datos de un volumen importante, como ser campos del tipo image, varbinary o ntext, los cuales por lo general no son necesarios recuperar cuando se carga un listado de la entidad en un grid, pero si son necesarios cuando se edita una entidad individual.

Por supuesto no solo esta limitado a este uso, se podría dividir simplemente porque un conjunto de propiedades representan la información clave de la entidad la cual es frecuentemente utilizada, separándola de aquellos atributos donde, salvo en algún uso puntual, no siempre se requieren.

Esta división permite mapear una tabla simple en múltiples entidades.

 

Complex Type Vs Table Splitting

Seguramente se estén preguntando, porque simplemente no se usa un Tipo Complejo? definiendo en este las propiedades que se consideran especiales.

El tema pasa porque los Tipo Complejos no permiten la carga retardada (lazy load), mientras que la división de la tabla en varias entidades si lo permite.

Al recuperar una entidad los tipo complejo serán siempre devueltos, no hay control sobre esta entidad, mientras que una tabla dividida en entidades se le puede indicar cuando se necesita recuperar este bloque de datos.

 

Definición del modelo


El objetivo es permitir separar un conjunto de propiedades en una entidad distinta

image

a pesar de esta dividido en varias entidades todas son mapeadas a la misma tabla, por lo que resulta

image

 

public class Employee
{
    public Employee()
    {
        this.Localization = new Localization();
    }

    public int EmployeeID { get; set; }

    public string LastName { get; set; }
    public string FirstName { get; set; }

    public virtual Localization Localization { get; set; }

    public virtual EmployeeExtended EmployeeExt { get; set; }

}

public class EmployeeExtended
{
    public int EmployeeID { get; set; }

    public byte[] Photo { get; set; }
    public string PhotoPath { get; set; }

    public string Notes { get; set; }
    
}

La entidad principal define una propiedad que referencia a la entidad extendida, a la vez que ambas entidades definen la misma propiedad como key, ya que por medio de esta se relacionan.

 

Definición del Mapping


Al definir el mapping ambas entidades persisten en la misma tabla, por eso el uso del ToTable(). También hacen uso de la misma key.

El HasRequired() permite indicar cual será la propiedad que representaran los datos extendidos en la entidad principal.

 

public class EmployeeMap : EntityTypeConfiguration<Employee>
{
    public EmployeeMap()
    {
        ToTable("Employees");
        HasKey(x => x.EmployeeID);

        Property(x => x.LastName)
            .HasMaxLength(20)
            .IsRequired();

        Property(x => x.FirstName)
            .HasMaxLength(10)
            .IsRequired();

        HasRequired(x => x.EmployeeExt)
                    .WithRequiredPrincipal();
    }
}

public class EmployeeExtendedMap : EntityTypeConfiguration<EmployeeExtended>
{
    public EmployeeExtendedMap()
    {
        ToTable("Employees");
        HasKey(x => x.EmployeeID);

        Property(x => x.Notes)
            .HasColumnType("ntext")
            .IsOptional();

        Property(x => x.Photo)
            .HasColumnType("image")
            .IsOptional();

        Property(x => x.PhotoPath)
            .HasColumnType("nvarchar")
            .HasMaxLength(255)
            .IsOptional();

    }
}

Definición del Repositorio


Hay cierta funcionalidad que requiere un tratamiento especial para estos casos es que se crea funcionalidad en el repositorio de le entidad empleado.

Se define esta en la interfaz, para luego implementarla en la clase concreta.

 

public interface IEmployeeRepository : IRepository<Employee>
{
    EmployeeExtended GetExtendedById(int id);

    void DeleteIncludeExtended(Employee entity);
}

 

public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository
{
    /// <summary>
    /// Recupera solo la entidad Extendida
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public EmployeeExtended GetExtendedById(int id)
    {
        using (NorthWindContext context = new NorthWindContext())
        {
            return context.EmployeeExtendeds.FirstOrDefault(x => x.EmployeeID == id);
        }
    }

    /// <summary>
    /// Elimina la entidad incluyendo la informacion extendida 
    /// en caso de tenerla
    /// </summary>
    /// <param name="entity"></param>
    public void DeleteIncludeExtended(Employee entity)
    {
        using (NorthWindContext context = new NorthWindContext())
        {

            if (entity.EmployeeExt == null)
                entity.EmployeeExt = new EmployeeExtended() { EmployeeID = entity.EmployeeID };

            context.Employees.Attach(entity);
            context.Employees.Remove(entity);

            context.SaveChanges();

        }
    }

}

Estos método serán utilizados en los Test, donde se probara su funcionalidad

 

Configuración del proyecto de Test


Para probar la funcionalidad se requiere que un archivo que representa la foto que será asignada a la propiedad de la entidad. Este archivo forma parte del proyecto de Test, pero debe ser copiado a la carpeta de output.

Sera necesario definir un archivo de TestSetting el cual no ayudara a configurar el entorno de prueba, para ello agregamos este archivo usando la opción

image

 

SNAGHTML4800a637

En este configuramos las opciones de deploy para que copie el archivo de la imagen a la carpeta que defina el test cuando ejecute

SNAGHTML480139af

Asignamos cual será el archivo usado en la ejecución de los test

image

 

SNAGHTML47f6aaaa

 

Test – Eliminar empleado con y sin información extendida


Si eliminamos una entidad haciendo uso del método Delete() del repositorio base obtendremos un error

image

Se produce porque se requiere no solo marcar para eliminar la entidad principal sino también la extendida, por esta razón en el repositorio de la entidad empleado se creo un método especifico para este caso.

El método DeleteIncludeExtended() elimina la entidad y también define la entidad extendida a pesar que no se envíe como dato en la entidad que se asigna como parámetro.

 

[TestMethod]
public void Delete_Employee()
{
    EmployeeRepository repoEmployee = new EmployeeRepository();

    //
    //se crea un empleado con info extendida
    //
    CreateEmployeeWithPhoto();

    //elimina el empleado y su informacion extendida
    repoEmployee.DeleteIncludeExtended(new Employee() { EmployeeID = employeeNew.EmployeeID } );

    //se recupera el empleado que fue eliminado en el paso anterior
    Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID);

    Assert.IsNull(employeeSel);


    //
    //se crean el empleados sin informacion extendida
    //
    Employee employee = new Employee()
    {
        FirstName = "name 1",
        LastName = "lastname 1",
        EmployeeExt = new EmployeeExtended()
    };
    repoEmployee.Create(employee);

    repoEmployee.DeleteIncludeExtended(employee);

    //se recupera el empleado que fue eliminado en el paso anterior
    employeeSel = repoEmployee.Single(x => x.EmployeeID == employee.EmployeeID);

    Assert.IsNull(employeeSel);

}

El test tiene dos partes, la primera crea un empleado con funcionalidad extendida y lo elimina usando el método que hemos creado en el repositorio para este propósito, la segunda parte crea una entidad sin información extendida y también elimina esta entidad, demostrando que sin importar la información con la cual se cree la entidad puede ser eliminada.

Como se vera para ambos casos es necesario definir el id de la entidad extendida para que sea marcada tanto la entidad principal como la extendida para ser eliminadas.

 

Test – Obtener todos los empleados


Se crean dos empleado, uno con información extendida y el otro sin ella.

En el test recupera la lista de empleados, pudiéndose analizarse como la información extendida no se obtiene, mientas que la propiedad que representa el complex type si es devuelta al recuperar la entidad.

Este caso es el típico que uno usaría para cargar un listado o grid con la información base de la entidad.

 

[TestMethod]
public void GetAll_Employee()
{
    string foto = Path.Combine(this.TestContext.DeploymentDirectory, "foto.jpg");

    EmployeeRepository repoEmployee = new EmployeeRepository();

    //se eliminan las entidades que pudieran quedar de la ejecucion de test anteriores
    repoEmployee.GetAll().ForEach(x => repoEmployee.DeleteIncludeExtended(x));


    //se crean dos empleados
    CreateEmployeeWithPhoto();

    Employee employee2 = new Employee()
    {
        FirstName = "name 2",
        LastName = "lastname 2",
        EmployeeExt = new EmployeeExtended()
    };
    repoEmployee.Create(employee2);


    List<Employee> list = repoEmployee.GetAll();

    Assert.AreEqual(list.Count, 2);

    Assert.IsNull(list[0].EmployeeExt);
    Assert.IsNotNull(list[0].Localization);
    Assert.IsTrue(list[0].Localization.HasValue);

    Assert.IsNull(list[1].EmployeeExt);
    Assert.IsNotNull(list[1].Localization);
}

Test – Obtener entidad SIN información extendida


Aunque se haya creado un empleado con información adicional al recuperar la entidad esta no forma parte de los datos.

 

[TestMethod]
public void GetById_Employee()
{
    //se crea el empleado con informacion extendida
    CreateEmployeeWithPhoto();


    //Se recupera la entidad
    Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID);


    Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
    Assert.IsTrue(employeeSel.Localization.HasValue);
    Assert.AreEqual(employeeSel.Localization.Address, "Address 1");
    Assert.AreEqual(employeeSel.Localization.City, "City 1");
    Assert.AreEqual(employeeSel.Localization.Country, "Country 1");

    Assert.IsNull(employeeSel.EmployeeExt);

}

La consulta recupera el empleado y deja evidencia de la inclusión de los campos que define el complex type pero no se incluyen los de la entidad extendida.

image

 

Test – Obtener entidad CON información extendida


En este caso el test define un “include” de la propiedad extendida, permitiendo recuperar la foto asignada al empleado

 

[TestMethod]
public void GetById_EmployeeExt()
{
    //se crea el empleado con informacion extendida
    CreateEmployeeWithPhoto();

    //se recupera la entidad y sus propiedad extendida
    Employee employeeSel = repoEmployee.Single(x => x.EmployeeID == employeeNew.EmployeeID,
                                                    new List<Expression<Func<Employee, object>>>() 
                                                    { 
                                                        x=>x.EmployeeExt 
                                                    });
                        


    Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
    Assert.IsTrue(employeeSel.Localization.HasValue);
    Assert.AreEqual(employeeSel.Localization.Address, "Address 1");
    Assert.AreEqual(employeeSel.Localization.City, "City 1");
    Assert.AreEqual(employeeSel.Localization.Country, "Country 1");

    Assert.IsNotNull(employeeSel.EmployeeExt);
    Assert.IsNotNull(employeeSel.EmployeeExt.Photo);
    Assert.AreEqual(employeeSel.EmployeeExt.PhotoPath, employeeNew.EmployeeExt.PhotoPath);

}

la consulta resultante es bastante mas compleja ya que debe unir los campos de la entidad extendida

image

Lo importante a destacar es que uno controla cuando es necesario recuperar los datos extendido para así mejorar la performance de la aplicación.

 

Test – Recuperar SOLO la información extendida


Si ya se dispone de la entidad con los datos básicos y se requiere solo los datos extendidos se podría recuperar únicamente estos.

 

[TestMethod]
public void GetById_Employee_RecoverOnlyExtended()
{

    CreateEmployeeWithPhoto();


    EmployeeExtended employeeSel = repoEmployee.GetExtendedById(employeeNew.EmployeeID);


    Assert.IsNotNull(employeeSel);
    Assert.AreEqual(employeeSel.EmployeeID, employeeNew.EmployeeID);
    Assert.IsNotNull(employeeSel.Photo);
    Assert.AreEqual(employeeSel.PhotoPath, employeeNew.EmployeeExt.PhotoPath);

}

El método GetExtendedById() hace uso de la propiedad del contexto que accede a esta entidad extendida de forma directa.

image

Es la misma consulta que recupera al empleado pero solo define los campos de la entidad extendida.

 

Documentación de referencia


 Associations in EF Code First: Part 4 – Table Splitting

 

Código


Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF

 

[C#]
 

viernes, 30 de agosto de 2013

[Entity Framework][Code First] Herencia - Tabla por tipo concreto - Table per Concrete Type (TPC)

 

Introducción


Se continua analizando las diferentes implementaciones de herencia que Entity Framework nos permite modelar.

En este caso cada tipo mapeara a una tabla la cual define todos los campos, ya sean los particulares que este defina, así como los declarados en la clase base.

Este artículo utilizara el mismo modelo de los ejemplos anteriores:

[Entity Framework][Code First] Herencia - Tabla por tipo - Table per Type (TPT)

[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)

 

Definición del modelo


Partiremos de un modelo de clases ya conocido en los artículos anteriores.

image

y obtendremos las tablas

image

La definición de cada clase derivada creara una tabla para si misma, llevando no solo las propiedades que esta defina como columnas sino que también lo hará con las de la clase base.

Un punto que se debe remarcar es que no hay relación entre las tablas.

En este ejemplo la clase Employee se define como “abstract” lo cual impide crear instancias de esta, es por eso que solo se crean dos tablas, si la base no fuera “abstract” se crearía una tercer tabla para soportar los datos de la entidad base.

 

Definición del Mapping


Para cada entidad derivada se define la clase de mapping donde se especifica el nombre de la tabla.

 

public class EmployeeInternalMap : EntityTypeConfiguration<EmployeeInternal>
{
    public EmployeeInternalMap()
    {

        Map(x =>
        {
            x.MapInheritedProperties();
            x.ToTable("InternalEmployee");
        });

      
    }
}

public class EmployeeExternalMap : EntityTypeConfiguration<EmployeeExternal>
{
    public EmployeeExternalMap()
    {

        Map(x =>
        {
            x.MapInheritedProperties();
            x.ToTable("ExternalEmployee");
        });

        Property(x => x.ConsultantName).IsRequired()
                                       .HasColumnType("varchar")
                                       .HasMaxLength(100);
    }
}

Se destaca del mapping la invocación al método MapInheritedProperties(), lo cual aplica un re-mapping las propiedades de la clase base.

image

 

Problema con la key de la tabla


Si los test los diseñamos en base a los artículos anteriores, al ejecutarlos obtendremos un error con la clave primaria.

SNAGHTML23be7cf7

La causa se debe a que este forma de mapear la herencia no define columnas identity para las claves, por medio de código se tendrá que asegurar el id secuencial que se asigna a la entidad cuando esta se persiste.

Para solucionar este problema se definió en la repositorio un método que nos ayudara a obtener el ultimo id. El método GetLastId() recupera el ultimo utilizado en la combinación de las tablas que definen la herencia.

Se lo declara primeramente en la interfaz.

public interface IEmployeeRepository : IRepository<Employee>
{
    List<EmployeeInternal> GetAllInternalType();

    List<Employee> GetAllExternalType();

    int GetLastId();

}

Y luego en la clase que implementa el repositorio.

 

  public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository
  {

      public int GetLastId()
      {
          using (NorthWindContext context = new NorthWindContext())
          {
              int? lastId = context.Employees.Max(x => (int?)x.EmployeeID);

              return lastId.HasValue ? lastId.Value : 0;
          }
      }

  }

 

La ejecución del mismo implica un UNION ALL entre las tablas que deriven de la misma base para luego aplicar el MAX() del campo definido como key

image 

 

Test – Inicializar datos


La inicialización requiere que por cada entidad creada se realice una consulta previa para recuperar el ultimo id

image

En la imagen se observa como a pesar de ser diferentes tablas se mantiene el consecutivo en la key.

 

Test – Obtener todos los empleados


La query generada para recuperar todos los empleados requiere unir las tablas de cada clase derivada.

image

 

Test – Recuperar todos los empleados del tipo interno


Ya sea usando un repositorio especial para el tipo concreto “GetAllInternal_UsingSpecificRepository_Employee()” o definiendo un método en el repositorio base el cual podria utilizar el OfType<>  “GetAllInternal_UsingGenericRepository_Employee()”, el resultado es idéntico, solo realiza la query sobre una tabla en particular donde se define el tipo.

 

image

 

Test – Obtener todos los empleados Externos


Al igual que los test anteriores, hacer uso de un repositorios especifico “GetAllExternal_Employee()” para recuperar los empleados externos o definir un método en el repositorio base que haga uso del “is” para definir que tipo de entidad cargar “GetAllExternal_UsandoIs_Employee()”, resulta en la mismo SELECT.

 

image

 

Resumen


Si bien en algunos casos este tipo de persistencia puede resultar útil, el hecho de no poder definir un campo clave como secuencial puede dificultar su implementación.

La creación de diferentes tablas generan redundancia de campos al duplicarlos, lo cual no lo hace un modelo de persistencia aconsejable, aunque puede que esta sea una característica buscada cuando no se requiere un modelo normalizado.

En resumen, analizar previamente los modelos de herencia TPT y TPH, dejando como ultima alternativa el TPC.

El modelo TPH hace uso de un discriminador para definir los tipos concretos. El TPT, lo realiza mediante la relación entre las tablas. En TPC en cambio la tabla completa define el tipo.

 

Documentación de referencia


Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)

 

Código


Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF

[C#]
 

miércoles, 28 de agosto de 2013

[Entity Framework][Code First] Herencia - Tabla por tipo - Table per Type (TPT)

 

Introducción


Analizaremos un modelo de herencia el cual permite definir una tabla para cada tipo concreto. En este caso la relación entre las tablas define el tipo de instancia de la entidad.

El modelo utilizado en este artículo es idéntico al anterior:

[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)

 

Definición del modelo


Modelaremos la entidad empleado y sus derivados que representan al personal externo y el contratado por la compañía.

Partiremos de un modelo de clases como el siguiente:

image

y obtendremos un modelo de tablas

image

en donde cada la clase base estará en una tabla mientras que los derivados, con sus atributos particulares, en otras distintas.

En este caso la relación entre las tablas define el tipo, a diferencia del modelo TPH (Tabla por jerarquía) en donde un campo era quien lo indicaba.

 

Definición del Mapping


Se va a necesitar definir en que tabla se persiste cada tipo especifico, es por ello que se requiere una clase de mapping para cada derivado que herede de la base.

 

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
        this.Configuration.LazyLoadingEnabled = false;
        this.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new EmployeeMap());

        modelBuilder.Configurations.Add(new EmployeeInternalMap());
        modelBuilder.Configurations.Add(new EmployeeExternalMap());

        base.OnModelCreating(modelBuilder);
    }

}

public class EmployeeMap : EntityTypeConfiguration<Employee>
{
    public EmployeeMap()
    {
        HasKey(x => x.EmployeeID);
        Property(x => x.LastName).HasMaxLength(20).IsRequired();
        Property(x => x.FirstName).HasMaxLength(10).IsRequired();

        Property(x => x.Address).HasMaxLength(60);
        Property(x => x.City).HasMaxLength(15);
        Property(x => x.Region).HasMaxLength(15);
        Property(x => x.PostalCode).HasMaxLength(10);
        Property(x => x.Country).HasMaxLength(15);

    }
}

public class EmployeeInternalMap : EntityTypeConfiguration<EmployeeInternal>
{
    public EmployeeInternalMap()
    {
        ToTable("EmployeeInternal");

      
    }
}

public class EmployeeExternalMap : EntityTypeConfiguration<EmployeeExternal>
{
    public EmployeeExternalMap()
    {
        ToTable("EmployeeExternal");

        Property(x => x.ConsultantName).IsRequired()
                                       .HasColumnType("varchar")
                                       .HasMaxLength(100);
    }
}

El principal detalle esta en la definición de los nombre de la tablas

image

 

Definición Repository


Este no sufrió cambios con respecto al articulo anterior donde tratamos TPH.

Nota: se agrego un método adicional a la interfaz del repositorio el cual implementa una query linq que utiliza el “is”, pero no varia la forma de definir esta funcionalidad con respecto al articulo anterior. Se analizara este punto más adelante cuando tratemos el Test que utiliza esta implementacion.

 

Test – Inicializar datos


La inicialización de datos es idéntica al artículo anterior donde explique TPH.

Pero el resultado de los “insert” que define EF son bastante diferentes, ya que por cada entidad creada requiere realiza dos operaciones, uno en la tabla base y otro en cada especialización, esto se aprecia claramente en el trace tomado desde el sql profiler.

image

 

Test – Obtener todos los empleados


Recuperar todos los empleados requiere implementar la unión de dos tablas, la de empleados internos y externos,  al ejecutar el test se puede observar claramente si analizamos el select definido por EF

image

La unión de las dos tablas que representan las entidades hijas se unen mediante un INNER JOIN con la tabla padre de empleados

Aquí nuevamente aparecen los “0X0X” y “0X1X” los cuales son usados por EF para determinar que tipo de instancia debe crear

 

Test – Recuperar todos los empleados de tipo interno, usando un repositorio especifico


Utilizamos el repositorio definido para un tipo concreto, en este caso se recuperan los empleados internos a la empresa

SNAGHTML31ecfb93

Esta acción unirá mediante un INNER JOIN la tabla que representa estos empleados con la tabla base, del cruce se obtendrán solo los registros de un único tipo.

 

image

 

Test – Recuperar todos los empleados del tipo interno, usando funcionalidad del repositorio genérico


Utilizar el método OfType<> definido en un método en al repositorio general

SNAGHTML31f473ce

dará el mismo resultado que utilizar el repositorio especifico del test anterior

image

 

Test – Obtener todos los empleados Externos


Se obtienes todos los empleados de contratación externa.

SNAGHTML32077f62

El join se realiza utilizando la tabla que representa los empleados externos a la compañía.

image

 

Test – Obtener todo los empleados externos, usando el “is”


Aplicaremos una variación en la técnica utilizada al definir el tipo que queremos filtrar, para ello nos ayudaremos con el “is” en el where de la query linq.

SNAGHTML321b1bf0

como veremos EF detecta el “is” como la acción de recuperar un tipo especifico, por eso el sql que define nuevamente hace uso del JOIN entre las tablas

image

 

Documentación de referencia


Inheritance with EF Code First: Part 2 – Table per Type (TPT)

 

Código


Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF

[C#]
 

lunes, 26 de agosto de 2013

[Entity Framework][Code First] Herencia - Tabla por jerarquía - Table per Hierarchy (TPH)

 

Introducción


Una de las principales ventajas al implementar un ORM, en nuestro caso de la mano de Entity Framework, apunta a tener a nuestra disposición todo el poder de la Programación Orientada a Objeto (POO) para modelar nuestras entidades de negocio.

La Herencia es una de las practicas mas utilizadas para modelar entidades, pudiendo representar en un modo realista el diseño del negocio, mejor aun si le unimos un fácil mapeo de las entidades con la estructura de la base de datos.

Existen tres formas de mapear una estructura de Herencia con tablas:

  1. Tabla por jerarquía - Table per Hierarchy (TPH)
  2. Tabla por tipo - Table per Type (TPT)
  3. Tabla por tipo concreto - Table per Concrete Type (TPC)

 

En este articulo tratare el primero de ellos, Tabla por jerarquía, en este las diferentes clases que definen la herencia mapean contra una única tabla en la base de datos utilizando un campo discriminador para determinar el tipo especifico.

 

Definición del modelo


En el ejemplo definiremos una entidad Empleado pudiendo encontrarse dos tipo: los empleados internos de la empresa y los de contratación externa.

 

public abstract class Employee
{
    public int EmployeeID { get; set; }

    public string LastName { get; set; }
    public string FirstName { get; set; }

    public string Address { get; set; }
    public string City { get; set; }
    public string Region { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }

}

public class EmployeeExternal : Employee
{
    public string ConsultantName { get; set; }

    public DateTime? ContactExpiration { get; set; }
}

public class EmployeeInternal : Employee
{
    public DateTime? HireDate { get; set; }
}

 

En al definición de las clases pueden observarse dos detalles:

  1. la clase base se define como abstract, este impedirá crear instancias del tipo base, con lo cual se obliga a crear instancias solo de los tipos derivados.
  2. las clases hijas poseen propiedades concretas para cada tipo que definen características determinadas, es recomendable que las propiedades de las clases derivadas permitan nulos o sino asignarle un valor por default.

La idea es poder mapear el modelo de objetos como el siguiente

image

generando una tabla como ser

image

 

Definición del Mapping


Definir como se debe persistir este tipo de modelo es bastante simple y no difiere a lo ya aprendido en los anteriores artículos que realice sobre el tema.

Se define el contexto de EF

public class NorthWindContext : DbContext
{

    public NorthWindContext()
        : base("NorthwindDb")
    {
        this.Configuration.LazyLoadingEnabled = false;
        this.Configuration.ProxyCreationEnabled = false;
    }

    public DbSet<Employee> Employees { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new EmployeeMap());

        base.OnModelCreating(modelBuilder);
    }

}

public class EmployeeMap : EntityTypeConfiguration<Employee>
{
    public EmployeeMap()
    {
        HasKey(x => x.EmployeeID);
        Property(x => x.LastName).HasMaxLength(20).IsRequired();
        Property(x => x.FirstName).HasMaxLength(10).IsRequired();

        Property(x => x.Address).HasMaxLength(60);
        Property(x => x.City).HasMaxLength(15);
        Property(x => x.Region).HasMaxLength(15);
        Property(x => x.PostalCode).HasMaxLength(10);
        Property(x => x.Country).HasMaxLength(15);

        Map<EmployeeInternal>(x => x.Requires("Type")
                                        .HasValue("I")
                                        .HasColumnType("char")
                                        .HasMaxLength(1));

        Map<EmployeeExternal>(x => x.Requires("Type")
                                        .HasValue("E"));

    }
}

 

Para la definición de la  herencia se debe puntualizar estas líneas:

image

En ellas se define el nombre del campo que actuara como discriminador del tipo, así como los valores que tomara para cada clase hija definida, opcionalmente se puede especificar el tipo y precisión de la columna.

Sino se especifica el tipo para la columna del discriminador Entity Framework usara valores por defecto, por lo que la columna podrías crearse como nvarchar(128), esto puede resultar de poca importancia, pero si solo se va a contener un único carácter se estaría desperdiciando espacio para ese campo.

El campo definido como discriminador no se define como propiedad en las clases de la entidad de dominio, ya que la propia instancia de la clase define el tipo en si mismo.

 

Definición Repository


La definición del repositorio tiene algunas novedades respecto a los artículos anteriores, en este caso junto al proyecto que de entidades se define la interfaz para poder extender el repositorio.

Quizás en este momento no se aprecie este tipo de implementación, pero si se hace uso de algún framework de IoC (Invertion of Control) como ser Ninject, Unity, etc, allí si se requieren interfaces para poder desacoplar la creación del repositorio concreto.

 

public interface IEmployeeRepository : IRepository<Employee>
{
    List<EmployeeInternal> GetAllInternalType();
}

 

public class EmployeeRepository : BaseRepository<Employee>, IEmployeeRepository
{
    /// <summary>
    /// Retorna todos los empleados externos a la empresa
    /// </summary>
    /// <returns></returns>
    public List<EmployeeInternal> GetAllInternalType()
    {
        using (NorthWindContext context = new NorthWindContext())
        {
            return context.Employees.OfType<EmployeeInternal>().ToList();
        }
    }

}

public class EmployeeInternalRepository : BaseRepository<EmployeeInternal>
{

}

public class EmployeeExternalRepository : BaseRepository<EmployeeExternal>
{

}

Test – Inicializar datos


En si mismo la inicialización de los datos no son un test, pero como todos los test harán uso de un mismo conjunto de datos se podría decir que la inicialización también aplica pruebas en al creación de las entidades.

La ejecución de esta inicialización implica la validación de los métodos de creación de las entidades.

 

private void InitializeTestData()
{
    IEmployeeRepository repoEmployee = new EmployeeRepository();
    
    //
    // elimino registros previos
    //
    List<Employee> list = repoEmployee.GetAll();

    list.ForEach(x => repoEmployee.Delete(x));


    //
    // creo un empleado interno
    //
    employee1 = new EmployeeInternal()
    {
        FirstName = "name1",
        LastName = "lastname1",
        HireDate = DateTime.Now.AddMonths(-10)
    };
    repoEmployee.Create(employee1);

    //
    // creo un empleado externo
    //
    employee2 = new EmployeeExternal()
    {
        FirstName = "name2",
        LastName = "lastname2",
        ConsultantName = "ConsultantName2",
        ContactExpiration = DateTime.Now.AddYears(2)
    };
    repoEmployee.Create(employee2);

    //
    // creo otro empleado externo
    //
    employee3 = new EmployeeExternal()
    {
        FirstName = "name3",
        LastName = "lastname3",
        ConsultantName = "ConsultantName3",
        ContactExpiration = DateTime.Now.AddYears(1)
    };
    repoEmployee.Create(employee3);
}

La inicialización de los datos ejecuta instrucciones sql donde se pueden observar el campo definido como discriminador

image

 

Test – Obtener todos los empleados


Obtendremos la lista de todos los empleados pudiendo validar el tipo de cada uno de ellos.

 

[TestMethod]
public void GetAll_Employee()
{
    InitializeTestData();

    //
    //recupero todos los empleados
    //
    IEmployeeRepository repoEmployee = new EmployeeRepository();

    List<Employee> listIntEmployee = repoEmployee.GetAll();

    //
    // Assert
    //
    Assert.AreEqual(listIntEmployee.Count, 3);

    Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));
    Assert.IsInstanceOfType(listIntEmployee[1], typeof(EmployeeExternal));
    Assert.IsInstanceOfType(listIntEmployee[2], typeof(EmployeeExternal));

}

image

El query creado para recuperar todos los empleados incluye un filtro que especifica todos los tipos existentes.

 

Test – Recuperar todos los empleados de tipo interno, usando un repositorio especifico


Se recuperan las entidades que corresponden a empleados propios de la empresa, pero para lograrlo se hace uso del repositorio definido para ese tipo concreto.

Se define un repositorio concreto para la clase EmployeeInternal, pudiendo utilizar los métodos que define el RepositorioBase<>

 

[TestMethod]
public void GetAllInternal_UsingSpecificRepository_Employee()
{
    InitializeTestData();

    //
    //recupero solo empleados internos
    //
    IRepository<EmployeeInternal> repoInternalEmployee = new EmployeeInternalRepository();

    List<EmployeeInternal> listIntEmployee = repoInternalEmployee.GetAll();

    //
    // Assert
    //
    Assert.AreEqual(listIntEmployee.Count, 1);

    Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));

    Assert.AreEqual(listIntEmployee[0].FirstName, employee1.FirstName);
    Assert.IsNotNull(listIntEmployee[0].HireDate);
    Assert.AreEqual(listIntEmployee[0].HireDate.Value.ToShortDateString(), employee1.HireDate.Value.ToShortDateString());

}

image

Al recuperar todas las instancia para un tipo en concreto la query filtra por el identificado definido para ese tipo.

Seguramente se preguntaran que significa el

'0X0X' AS [C1],

esa línea es usada internamente por Entity Framework para saber la instancia de que tipo en concreto tiene que materializar, o sea es una marca que define el tipo.

Test – Recuperar todos los empleados del tipo interno, usando funcionalidad del repositorio genérico


Se recuperan las entidades que corresponden a empleados propios de la empresa, pero en este caso se utilizara el repositorio definido para la clase base.

Es por medio del OfType<> que se especifica que tipo concreto se quiere recuperar.

 

[TestMethod]
public void GetAllInternal_UsingGenericRepository_Employee()
{

    InitializeTestData();

    //
    //recupero solo empleados internos
    //
    IEmployeeRepository repoEmployee = new EmployeeRepository();

    List<EmployeeInternal> listIntEmployee = repoEmployee.GetAllInternalType();

    //
    // Assert
    //
    Assert.AreEqual(listIntEmployee.Count, 1);

    Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));

    Assert.AreEqual(listIntEmployee[0].FirstName, employee1.FirstName);
    Assert.IsNotNull(listIntEmployee[0].HireDate);
    Assert.AreEqual(listIntEmployee[0].HireDate.Value.ToShortDateString(), employee1.HireDate.Value.ToShortDateString());

}

image

Usar el OfType<> genera el mismo resultado que especializar el repositorio, la query generada son idénticas

 

Test – Obtener todos los empleados Externos


Se obtienes todos los empleados de contratación externa.

 

[TestMethod]
public void GetAllExternal_Employee()
{

    InitializeTestData();

    //
    //recupero solo empleados externos
    //
    IRepository<EmployeeExternal> repoExternalEmployee = new EmployeeExternalRepository();

    List<EmployeeExternal> listExtEmployee = repoExternalEmployee.GetAll();

    //
    // Assert
    //
    Assert.AreEqual(listExtEmployee.Count, 2);

    Assert.IsInstanceOfType(listExtEmployee[0], typeof(EmployeeExternal));
    Assert.IsInstanceOfType(listExtEmployee[1], typeof(EmployeeExternal));

    Assert.AreEqual(listExtEmployee[0].FirstName, employee2.FirstName);
    Assert.IsNotNull(listExtEmployee[0].ContactExpiration);
    Assert.AreEqual(listExtEmployee[0].ContactExpiration.Value.ToShortDateString(), employee2.ContactExpiration.Value.ToShortDateString());
    Assert.AreEqual(listExtEmployee[0].ConsultantName, employee2.ConsultantName);

    Assert.AreEqual(listExtEmployee[1].FirstName, employee3.FirstName);
    Assert.IsNotNull(listExtEmployee[1].ContactExpiration);
    Assert.AreEqual(listExtEmployee[1].ContactExpiration.Value.ToShortDateString(), employee3.ContactExpiration.Value.ToShortDateString());
    Assert.AreEqual(listExtEmployee[1].ConsultantName, employee3.ConsultantName);
}

image

 

 

Poder crear instancias de la clase base


Al comienzo del artículo comente que la clase base se define con abstract para así forzar siempre usar las derivadas, al generar la tabla el campo que actúa como discriminador no permita nulo.

Ahora si queremos crear instancias de la clase base, solo será cuestión de permitirlo quitando el abstract.

 

image

la tabla generada por EF ahora permite nulo en el campo “Type”

image

con lo cual se podrán crear instancias del tipo base “Employee”, para probarlo creamos un test

[TestMethod]
public void GetAll_WithBaseType_Employee()
{
    IEmployeeRepository repoEmployee = new EmployeeRepository();

    //
    // creo un empleado interno
    //
    EmployeeInternal employee1 = new EmployeeInternal()
    {
        FirstName = "name1",
        LastName = "lastname1",
        HireDate = DateTime.Now.AddMonths(-10)
    };
    repoEmployee.Create(employee1);

    //
    // creo un empleado interno
    //
    Employee employee2 = new Employee()
    {
        FirstName = "name2",
        LastName = "lastname2"
    };
    repoEmployee.Create(employee2);

    //
    //recupero todos los empleados
    //

    List<Employee> listIntEmployee = repoEmployee.GetAll();

    //
    // Assert
    //
    Assert.AreEqual(listIntEmployee.Count, 2);

    Assert.IsInstanceOfType(listIntEmployee[0], typeof(EmployeeInternal));
    //validamos los tipos base de cada objeto recuperado
    Assert.AreEqual(listIntEmployee[0].GetType().BaseType, typeof(Employee));
    Assert.AreEqual(listIntEmployee[1].GetType().BaseType, typeof(object));

}

Al final del test se valida los tipo base de cada instancia, para el empleado interno será la clase “Employee”, pero para una instancia base del empleado al no derivar de ninguna otra será el tipo “object”.

El query generado en este caso es bastante mas complejo

image

por eso de ser posible definir la herencia para usar solo las clases hijas

 

Campos discriminador numérico


Además de definir un campo discriminador del tipo string o char, también se puede definir numérico, solo hay que especificar los valores en el mapping

image

la creación de la tabla cambiara el campo “Type” como numérico

image

 

Documentación de referencia


Inheritance with EF Code First: Part 1 – Table per Hierarchy (TPH)

 

Código


Se utilizo Visual Studio 2012, no se adjunta ninguna base de datos, esta será creada al ejecutar los test utilizando el modelo definido en el contexto de EF

[C#]