Inheritance mapping strategies in 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):
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.
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 the DisciriminateSubClassesOnColumn specifies the discriminator database filed that determines the kind of the animal.
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.
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 a 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, the name of the schema, to which the table belongs. Mapping for all the derived TPTMammalMap, TPTReptileMap, TPTCrocodileMap, TPTSnakeMap, TPTDogMap и 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.
In TPC inheritance, every class in a 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 a 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 contains 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.
Post Comment
dWKYWI This is very interesting, You are a very skilled blogger. I ave joined your rss feed and look forward to seeking more of your great post. Also, I have shared your site in my social networks!
oCxLD9 Wow that was unusual. I just wrote an really long comment but after I clicked submit my comment
AUAtBS Thanks again for the article post.Really thank you! Fantastic.
WoPH86 Thanks-a-mundo for the article post.Much thanks again. Really Great.
aMYYdC This is one awesome article.Thanks Again. Awesome.
veg91A Very good post.Really looking forward to read more. Will read on...
6ZszpQ Say, you got a nice article post.Really thank you!