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.  

Example: 

The database contains the TPH_Animal table:  

 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: 

2.png 

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: 

 

3.png

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:

 

4.png

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: 

5.png

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:   

6.png

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. 

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"