Introduction
This article explains and illustrates, on a practical example, how the support of different inheritance mapping strategies is implemented
in Entity Developer with Fluent NHibernate.
The support mapping strategies are the following:
- Table Per Hierarchy (TPH);
- Table Per Type (TPT);
- Table Per Concrete Class (TPC).
Table Per Hierarchy (TPH)
In TPH, the inheritance tree is created through one table only. The TPH inheritance depends on a conditional mapping which is defined by a condition such as a discriminator
database field. The condition is used to define records as different types.
For example, the database contains the TPH_Animal table:
The ClassType field is the discriminator and determines the kind of the animal: Crocodile, Snake, Dog, or Horse.
We perform the following sequence of operations: create an NHibernate model, create the class structure and inheritances, add the additional Fluent NHibernate template
to generate Fluent mapping. In the result, we have the following model:
Below is the generated Fluent mapping for this model:
public class DogMap : SubclassMap<Dog>
{
public DogMap()
{
DiscriminatorValue(@"Dog");
Map(x => x.Breed)
.Column("Breed")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
}
}
public class SnakeMap : SubclassMap<Snake>
{
public SnakeMap()
{
DiscriminatorValue(@"Snake");
Map(x => x.Length)
.Column("Length")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(5)
.Scale(2);
Map(x => x.IsAdder)
.Column("IsAdder")
.CustomType("Boolean")
.Access.Property()
.Generated.Never()
.CustomSqlType("bit");
}
}
public class HorseMap : SubclassMap<Horse>
{
public HorseMap()
{
DiscriminatorValue(@"Horse");
Map(x => x.MaximumSpeed)
.Column("MaximumSpeed")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(4)
.Scale(2);
}
}
public class CrocodileMap : SubclassMap<Crocodile>
{
public CrocodileMap()
{
DiscriminatorValue(@"Crocodile");
Map(x => x.Weight)
.Column("Weight")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Not.Nullable()
.Precision(4)
.Scale(2);
Map(x => x.Length)
.Column("Length")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(5)
.Scale(2);
}
}
public class TPHAnimalMap : ClassMap<TPHAnimal>
{
public TPHAnimalMap()
{
Schema("dbo");
Table("TPH_Animal");
Id(x => x.ID)
.Column("ID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
DiscriminateSubClassesOnColumn("ClassType").Not.Nullable();
Map(x => x.FoodClassification)
.Column("FoodClassification")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Not.Nullable()
.Length(128);
Map(x => x.BirthDate)
.Column("BirthDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.Family)
.Column("Family")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
Map(x => x.Genus)
.Column("Genus")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
}
}
The code above represents the generated model mapping classes. The mapping of the derived CrocodileMap
, DogMap
, and HorseMap
classes
is the implementation of mapping for the properties that are contained only in these classes, while the DiscriminatorValue
function specifies the value that
determines whether a record belongs to this type.
The TPHAnimalMap
class that determines the mapping of the base class describes general mapping for all derived classes; this mapping contains the definition
of the name of the table in the database, the name of the schema to which the table belongs, the descriptions of the identity key mapping and of general
properties, while DisciriminateSubClassesOnColumn
specifies the discriminator database field that determines the kind of the animal.
Table Per Type(TPT)
TPT is an inheritance described in the database with separate tables. Every table provides additional details that describe a new type based on another table which
is that table’s parent.
For example, the database contains the following tables: TPT_Animal, TPT_Mammal, TPT_Reptile, TPT_Horse, TPT_Snake, TPT_Crocodile, and TPT_Dog:
We perform the following sequence of operations: first, we create an NHibernate model, create the class structure and inheritances, add
the optional Fluent NHibernate template to generate Fluent mapping. In the result, we get the following model:
The generated Fluent mapping for this model is as follows:
public class TPTHorseMap : SubclassMap<TPTHorse>
{
public TPTHorseMap()
{
Schema("dbo");
Table("TPT_Horse");
KeyColumn("ID");
Map(x => x.MaximumSpeed)
.Column("MaximumSpeed")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(4)
.Scale(2);
}
}
public class TPTDogMap : SubclassMap<TPTDog>
{
public TPTDogMap()
{
Schema("dbo");
Table("TPT_Dog");
KeyColumn("ID");
Map(x => x.Breed)
.Column("Breed")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Not.Nullable()
.Length(128);
}
}
public class TPTMammalMap : SubclassMap<TPTMammal>
{
public TPTMammalMap()
{
Schema("dbo");
Table("TPT_Mammal");
KeyColumn("ID");
Map(x => x.BirthDate)
.Column("BirthDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime")
.Not.Nullable();
}
}
public class TPTCrocodileMap : SubclassMap<TPTCrocodile>
{
public TPTCrocodileMap()
{
Schema("dbo");
Table("TPT_Crocodile");
KeyColumn("ID");
Map(x => x.Family)
.Column("Family")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
Map(x => x.Genus)
.Column("Genus")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
}
}
public class TPTSnakeMap : SubclassMap<TPTSnake>
{
public TPTSnakeMap()
{
Schema("dbo");
Table("TPT_Snake");
KeyColumn("ID");
Map(x => x.IsAdder)
.Column("IsAdder")
.CustomType("Boolean")
.Access.Property()
.Generated.Never()
.CustomSqlType("bit")
.Not.Nullable();
}
}
public class TPTReptileMap : SubclassMap<TPTReptile>
{
public TPTReptileMap()
{
Schema("dbo");
Table("TPT_Reptile");
KeyColumn("ID");
Map(x => x.Length)
.Column("Length")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Not.Nullable()
.Precision(5)
.Scale(2);
}
}
public class TPTAnimalMap : ClassMap<TPTAnimal>
{
public TPTAnimalMap()
{
Schema("dbo");
Table("TPT_Animal");
Id(x => x.ID)
.Column("ID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
Map(x => x.Weight)
.Column("Weight")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Not.Nullable()
.Precision(4)
.Scale(2);
Map(x => x.FoodClassification)
.Column("FoodClassification")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Not.Nullable()
.Length(128);
}
}
The code above represents the generated model mapping classes. The TPTAnimalMap
class that determines the mapping of the base class itself
describes general mapping for all derived classes; general mapping contains the descriptions of the identity key mapping and general properties, as well as the
definitions of the name of the table in the database and the name of the schema to which the table belongs. Mapping for all the derived TPTMammalMap
, TPTReptileMap
,
TPTCrocodileMap
, TPTSnakeMap
, TPTDogMap
, and TPTHorseMap
classes determines the mapping of properties that only these classes have,
the name of the table in the database that corresponds to this type, the name of the schema which the table belongs to, while the KeyColumn
function specifies
the name of the column against which the foreign key is created in the database, together with the table corresponding to the base class.
Table Per Concrete Class (TPC)
In TPC inheritance, every class in an inheritance hierarchy will have its own table. The inheritance hierarchy masks the fact that there are several independent underlying tables
representing each subtype.
In the database, general properties can be repeated in every table, instead of being put into a separate table. In this case, the base class will be abstract.
We shall consider this very case:
The database contains the TPC_Horse and TPC_Dog tables that have some identical fields:
We create an NHibernate model, create the class structure and inheritances, and add the optional Fluent NHibernate template to generate Fluent mapping.
In the result, we get the following model:
The generated Fluent mapping for this model is as follows:
public class TPCDogMap : SubclassMap<TPCDog>
{
public TPCDogMap()
{
Schema("dbo");
Table("TPC_Dog");
Abstract();
Map(x => x.Breed)
.Column("Breed")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
}
}
public class TPCHorseMap : SubclassMap<TPCHorse>
{
public TPCHorseMap()
{
Schema("dbo");
Table("TPC_Horse");
Abstract();
Map(x => x.MaximumSpeed)
.Column("MaximumSpeed")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(4)
.Scale(2);
}
}
public class TPCBaseEntityMap : ClassMap<TPCBaseEntity>
{
public TPCBaseEntityMap()
{
UseUnionSubclassForInheritanceMapping();
Id(x => x.ID)
.Column("ID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Assigned();
Map(x => x.BirthDate)
.Column("BirthDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime");
Map(x => x.Genus)
.Column("Genus")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
}
}
The code above represents the generated model mapping classes. The mapping of the derived TPCDogMap
and TPCHorseMap
implements the mapping of the properties
that only these classes have and defines the name of the table in the database corresponding to this type, the name of the schema which the table belongs to,
while the Abstract
function indicates that the base class is abstract.
The TPСBaseEntityMap
, defining the mapping of the base class, describes the general mapping of all the derived classes that contain the description of the mapping
for the identity key and general properties; the UseUnionSubclassForInheritanceMapping
function indicates that this class is the base one for the TPC inheritance strategy
and that the values of its properties should be united with the values of derived classes. In the case when general properties are put into a separate table in the database,
the base class should be mapped to that table by specifying in the mapping the name of the table in the database as well as the name of the schema which the table belongs to.
To demonstrate how to implement and use inheritances along with many other kinds of NHibernate mapping, we release NHibernate Mapping Samples application, which demonstrates 50 different mapping cases, how they are mapped using both fluent and XML mapping, and their usage. Visit our NHibernate Mapping Samples blog article to read more about this application and download it.
Related articles