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:
- Tabla por jerarquía - Table per Hierarchy (TPH)
- Tabla por tipo - Table per Type (TPT)
- 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:
- 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.
- 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

generando una tabla como ser

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:

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

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));
}

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());
}

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());
}

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);
}

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.

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

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

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

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

Documentación de referencia
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