Inheritance mapping strategies in Fluent Nhibernate






4.76/5 (20 votes)
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.
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:
// mapping of the Dog class
public class DogMap : SubclassMap<Dog>
{
public DogMap()
{
// Here we define the discriminator value "Dog"
// to differentiate records of the class Dog
DiscriminatorValue(@"Dog");
// using the Map function, we define the mapping
// of the Breed property, it is possible to define
// the type of the property, its access, the name
// of the filed in the table and its server type,
// facets and other mapping settings
Map(x => x.Breed)
.Column("Breed")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
}
}
// mapping of the Shake class
public class SnakeMap : SubclassMap<Snake>
{
public SnakeMap()
{
// Here we define the discriminator value "Snake"
// to differentiate records of the class Snake
DiscriminatorValue(@"Snake");
// Using the Map functions, we define mapping
// of the Length и IsAdder properties; it is possible
// to specify the type of the property, its access,
// the name of the field in the table
// and its server type, facets and other mapping settings
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");
}
}
// mapping of the Horse class
public class HorseMap : SubclassMap<Horse>
{
public HorseMap()
{
// Here we define the discriminator value "Horse"
// to differentiate records of the class Horse
DiscriminatorValue(@"Horse");
// Using the Map functions, we define mapping
// of the MaximumSpeed property, it is possible to
// specify the type of the property, its access,
// the name of the field in the table and its
// server type, facets and other mapping settings
Map(x => x.MaximumSpeed)
.Column("MaximumSpeed")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(4)
.Scale(2);
}
}
// mapping of the Crocodile class
public class CrocodileMap : SubclassMap<Crocodile>
{
public CrocodileMap()
{
// Here we define the discriminator value
// "Crocodile" to differentiate records of
// the class Crocodile
DiscriminatorValue(@"Crocodile");
// Using the Map functions, we define mapping
// of the Weight и Length properties, it is possible
// to specify the type of the property,
// its access, the name of the field in the table
// and its server type, facets and other mapping settings
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);
}
}
// mapping of the base class TPHAnimal, which contains
// properties that are common for all classes
public class TPHAnimalMap : ClassMap<TPHAnimal>
{
public TPHAnimalMap()
{
// the name of the schema that contains the table
Schema("dbo");
// the name of the table for TPT inheritance
Table("TPH_Animal");
// the Id function is used for identity key mapping,
// it is possible to specify the type of
// the property, its access, the name
// of the field in the table and its server type,
// facets and other mapping settings,
// as well as to specify the class name to be used to
// generate the primary key for a new record while saving a new record
Id(x => x.ID)
.Column("ID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
// here we specify the name of the column
// that will define the type of the animal
DiscriminateSubClassesOnColumn("ClassType").Not.Nullable();
// Using the Map function, we define mapping
// of common properties; it is possible to specify
// the type of the property, its access,
// the name of the field in the table and its server
// type, facets and other mapping settings
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:
// mapping of the TPTHorse class
public class TPTHorseMap : SubclassMap<TPTHorse>
{
public TPTHorseMap()
{
// the name of the schema that stores the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPT_Horse");
// the name of the column, against which
// the foreign key is created in the database together
// with the table TPTMammal
KeyColumn("ID");
// Using the Map function, we define mapping
// of the MaximumSpeed property, it is possible
// to specify the type of the property,
// its access, the name of the field in the table
// and its server type, facets and other mapping settings
Map(x => x.MaximumSpeed)
.Column("MaximumSpeed")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(4)
.Scale(2);
}
}
// mapping of the TPTDog class
public class TPTDogMap : SubclassMap<TPTDog>
{
public TPTDogMap()
{
// the name of the schema that stores the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPT_Dog");
// the name of the column, against which
// the foreign key is created in the database together
// with the table TPTMammal
KeyColumn("ID");
// Using the Map function, we define mapping
// of the Breed property, it is possible to
// specify the type of the property, its access,
// the name of the field in the table and its
// server type, facets and other mapping settings
Map(x => x.Breed)
.Column("Breed")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Not.Nullable()
.Length(128);
}
}
// mapping of the TPTMammal class, which is the base class
// for the TPTDog and TPTHorse classes
public class TPTMammalMap : SubclassMap<TPTMammal>
{
public TPTMammalMap()
{
// the name of the schema that stores the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPT_Mammal");
// the name of the column, against which
// the foreign key is created in the database together
// with the table TPTAnimal
KeyColumn("ID");
// Using the Map function, we define mapping
// of the Mammals property BirthDate, it is possible
// to specify the type of the property, its access,
// the name of the field in the table
// and its server type, facets and other mapping settings
Map(x => x.BirthDate)
.Column("BirthDate")
.CustomType("DateTime")
.Access.Property()
.Generated.Never()
.CustomSqlType("datetime")
.Not.Nullable();
}
}
// mapping of the TPTCrocodile class
public class TPTCrocodileMap : SubclassMap<TPTCrocodile>
{
public TPTCrocodileMap()
{
// the name of the schema that stores the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPT_Crocodile");
// the name of the column, against which
// the foreign key is created in the database together
// with the table TPTReptile
KeyColumn("ID");
// Using the Map function, we define mapping
// of the Family и Genus properties, it is possible
// to specify the type of the property, its access,
// the name of the field in the table and
// its server type, facets and other mapping settings
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);
}
}
// mapping of the TPTSnake class
public class TPTSnakeMap : SubclassMap<TPTSnake>
{
public TPTSnakeMap()
{
// the name of the schema that stores the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPT_Snake");
// the name of the column, against which
// the foreign key is created in the database together
// with the table TPTReptile
KeyColumn("ID");
// Using the Map function, we define mapping of the IsAdder property, it is possible to
// specify the type of the property, its access,
// the name of the field in the table and
// its server type, facets and other mapping settings
Map(x => x.IsAdder)
.Column("IsAdder")
.CustomType("Boolean")
.Access.Property()
.Generated.Never()
.CustomSqlType("bit")
.Not.Nullable();
}
}
// mapping of the TPTReptile class, which is the base
// one for the TPTSnake and TPTCrocodile classes
public class TPTReptileMap : SubclassMap<TPTReptile>
{
public TPTReptileMap()
{
// the name of the schema that stores the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPT_Reptile");
// the name of the column, against which
// the foreign key is created in the database together
// with the table TPTAnimal
KeyColumn("ID");
// Using the Map function, we define mapping
// for the reptile-common property Length, it is
// possible to specify the type of the property,
// its access, the name of the field in the
// table and its server type, facets and other mapping settings
Map(x => x.Length)
.Column("Length")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Not.Nullable()
.Precision(5)
.Scale(2);
}
}
// mapping of the TPTAnimal base class
public class TPTAnimalMap : ClassMap<TPTAnimal>
{
public TPTAnimalMap()
{
// the name of the schema that stores the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPT_Animal");
// the Id function is used for identity key mapping,
// it is possible to specify the type of
// the property, its access, the name of the field
// in the table and its server type, facets
// and other mapping settings, as well as to specify
// the class name to be used to generate
// the primary key for a new record while saving a new record
Id(x => x.ID)
.Column("ID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Identity();
// Using the Map function, we define mapping
// of the common properties; it is possible to
// specify the type of the property, its access,
// the name of the field in the table and its
// server type, facets and other mapping settings
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:
// mapping of the TPCDog class
public class TPCDogMap : SubclassMap<TPCDog>
{
public TPCDogMap()
{
// the name of the schema that stores
// the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPC_Dog");
// indicates that the base class is abstract
Abstract();
// Using the Map function, we define mapping
// of the Breed property, it is possible to specify
// the type of the property, its access,
// the name of the field in the table and its server
// type, facets and other mapping settings
Map(x => x.Breed)
.Column("Breed")
.CustomType("String")
.Access.Property()
.Generated.Never()
.CustomSqlType("nvarchar")
.Length(128);
}
}
// mapping of the TPCHorse class
public class TPCHorseMap : SubclassMap<TPCHorse>
{
public TPCHorseMap()
{
// the name of the schema that stores
// the table corresponding to the type
Schema("dbo");
// the name of the table corresponding to the type
Table("TPC_Horse");
// indicates that the base class is abstract
Abstract();
// Using the Map function, we define mapping
// of the MaximumSpeed property, it is possible
// to specify the type of the property,
// its access, the name of the field in the table
// and its server type, facets and other mapping settings
Map(x => x.MaximumSpeed)
.Column("MaximumSpeed")
.CustomType("Decimal")
.Access.Property()
.Generated.Never()
.CustomSqlType("decimal")
.Precision(4)
.Scale(2);
}
}
// mapping of the TPCBaseEntity base class
public class TPCBaseEntityMap : ClassMap<TPCBaseEntity>
{
public TPCBaseEntityMap()
{
// 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
UseUnionSubclassForInheritanceMapping();
// the Id function is used for identity
// key mapping, it is possible to specify the type
// of the property, its access, the name
// of the field in the table and its server type,
// facets and other mapping settings,
// as well as to specify the class name to be used to
// generate the primary key for a new
// record while saving a new record
Id(x => x.ID)
.Column("ID")
.CustomType("Int32")
.Access.Property()
.CustomSqlType("int")
.Not.Nullable()
.Precision(10)
.GeneratedBy.Assigned();
// Using the Map function, we define mapping
// for common properties, it is possible to specify
// the type of the property, its access,
// the name of the field in the table and its server type,
// facets and other mapping settings
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.