Click here to Skip to main content
15,881,882 members
Articles / Web Development / ASP.NET

Inheritance mapping strategies in Fluent Nhibernate

Rate me:
Please Sign up or sign in to vote.
4.76/5 (20 votes)
7 Aug 2012CPOL4 min read 102.1K   36   7
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:

mapping-strategies-fluent/1.png

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:

mapping-strategies-fluent/2.png

Below is the generated Fluent mapping for this model:

C#
// 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:

mapping-strategies-fluent/3.png

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:

mapping-strategies-fluent/4.png

The generated Fluent mapping for this model is as follows:

C#
// 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:

mapping-strategies-fluent/5.png

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:

mapping-strategies-fluent/6.png

The generated Fluent mapping for this model is as follows:

C#
// 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.

Related articles 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Devart
Ukraine Ukraine
Igor Ignatov is a Senior Software Developer in Devart. He has more than 10 years of experience in working with different databases and is currently engaged in developing ADO.NET providers and design-time development tools for ORMs. Igor has been developing EntityDeveloper tool since 2008.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Jason Carney26-Oct-20 7:54
Jason Carney26-Oct-20 7:54 
QuestionNice and explanatory Pin
Ivaylo Slavov8-May-15 8:31
Ivaylo Slavov8-May-15 8:31 
QuestionTPC with non-abstract base Pin
Dean Cirielli15-Sep-13 4:42
Dean Cirielli15-Sep-13 4:42 
QuestionThank you!!! Pin
takuroandtakuro23-Oct-12 19:17
takuroandtakuro23-Oct-12 19:17 
QuestionExcellent Pin
Paul Tew5-Apr-12 6:42
Paul Tew5-Apr-12 6:42 
QuestionCreate Schema via NHibernate schemaupdate/export tool? Pin
Stephen Brannan10-Aug-11 7:22
Stephen Brannan10-Aug-11 7:22 
AnswerRe: Create Schema via NHibernate schemaupdate/export tool? Pin
Igor Ignatov10-Aug-11 21:22
Igor Ignatov10-Aug-11 21:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.