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#]
 

No hay comentarios:

Publicar un comentario