17 de mayo de 2013

Herencia de Modelos con Entity Framework Code First

En estos últimos días, hemos estado trabajando en unos prototipos para un cliente con MVC 3 y Entity Framework Code First. En el transcurso de este proyecto se nos vino a la mente una duda: ¿será posible heredarle un modelo a otro? Ya que si lo es, se podría eliminar el código redundante y se normalizaría la base de datos. Con esto en mente, nos pusimos a investigar.


Después de buscar una respuesta a nuestra pregunta, descubrimos que, en efecto, es posible heredar un modelo de otro y existen distintas maneras de llevarlo a cabo. En este post hablaremos de dos formas de herencia: por jerarquía y por tipo.

Tabla por Jerarquía

Pongamos como ejemplo una compañía que quiere llevar un registro de sus clientes al igual que de sus empleados. Usualmente veríamos una tabla para cada objeto, pero si tenemos campos en común en las tablas de cliente y empleado podemos aplicar la herencia por jerarquía o TPH (Table per Hierarchy).

En lugar de tener dos modelos (Cliente y Empleado), agregamos una clase Persona de la que van a heredar las otras dos clases. El diagrama de clases queda de la siguiente forma:


Nótese que en el DbContext sólo se define un DbSet de Persona. Code First de Entity Framework se encargará de encontrar las otras clases de la jerarquía.

public abstract class Persona
{
    public int PersonaId { get; set; }
    public string Nombre { get; set; }
    public string Apellido { get; set; }
    public DateTime FechaNacimiento { get; set; }
}

public class Cliente : Persona
{
    public string RFC { get; set; }
    public string Empresa { get; set; }
}

public class Empleado : Persona
{
    public string NumeroSeguro { get; set; }
    public decimal Sueldo { get; set; }
}
    
public class MapeoHerenciasContexto : DbContext
{
    public DbSet<Persona> Personas { get; set; } 
} 

Todas las clases de la jerarquía se pueden mapear a una sola tabla. Esta tabla incluye columnas para todas las propiedades de todas las clases de la jerarquía. Para identificar la subclase se utiliza una columna discriminadora. Aunque no sea una propiedad en nuestros modelos de EF, se utiliza internamente por EF Code First. Por defecto, el nombre de la columna es “Discriminator” y es de tipo string. Los valores que toma son los nombres de las clases de nuestro proyecto. EF Code First recupera y modifica los valores de esta columna automáticamente. 

TPH requiere que las propiedades de las subclases puedan ser nulas en la base de datos. También viola la tercera forma normal al utilizar la columna Discriminator y no siendo ésta una llave de la tabla lo que puede llevar a problemas de estabilidad y mantenimiento a la larga.

Tabla por Tipo

 Tabla por Tipo o TPT (Table per Type) representa las relaciones de herencia por medio de llaves foráneas. Todas las clases y subclases tienen su propia tabla. Las tablas de las subclases tienen las columnas que no son hereditarias junto con una llave primaria que actúa como llave foránea a la tabla de la clase base. Se vería de la siguiente forma en la base de datos:




Las ventajas de esta forma de herencia es que tenemos un esquema de base de datos normalizado y agregar más subclases es bastante sencillo. 

Podemos crear un mapeo TPT con sólo agregar el atributo Table a las subclases:

public abstract class Persona
{
    public int PersonaId { get; set; }
    public string Nombre { get; set; }
    public string Apellido { get; set; }
    public DateTime FechaNacimiento { get; set; }
}

[Table("Cliente")]
public class Cliente : Persona
{
    public string RFC { get; set; }
    public string Empresa { get; set; }
}

[Table("Empleado")]
public class Empleado : Persona
{
    public string NumeroSeguro { get; set; }
    public decimal Sueldo { get; set; }
}

public class MapeoHerenciasContexto : DbContext
{
    public DbSet<Persona> Personas { get; set; } 
}

Aunque ésta se vea como una forma de mapeo simple, puede llegar a ser muy engañosa. Los queries se pueden volver bastante complicados porque hay necesidad de hacer join a varias tablas. 

Conclusión

En este post hemos revisado dos maneras de hacer un mapeo por medio de herencia utilizando EF y Code First. TPH o por jerarquía, es el método por defecto de utiliza EF Code First para hacer un mapeo; las desventajas que presenta son el tener una base de datos desnormalizada y puede ser difícil de mantener a la larga. TPT o por tipo, utiliza una tabla por clase/subclase y establece las relaciones por medio de llaves foráneas; las queries para este tipo de mapeo pueden llegar a ser engañosas e ineficientes.

1 comentario:

  1. Me recuerda a xpo. Supongo que en realidad no hay muchas alternativas para este dilema :-P

    ResponderEliminar