Monday, 9 March 2009

Fluent NHibernate and Inheritance Mapping

While exploring the AdventureWorks database I have found an interesting case where inheritance mapping has to be used. In this post I would like to show how it can be neatly mapped with Fluent NHibernate. Lets start with database schema:

There are a few interesting things about this few tables:
  • There are two types of customers: Store and Individual
  • Each customer is represented as a single record in Customer table and additionally single record in Individual or Store table. Customer has a CustomerType column based on which we can determine the type.
  • Individual and Store tables have a primary key which is also a foreign key to the main Customer table. It means that Customer with ID 1231 will have corresponding record in Store (or Individual) table with the same ID.
Why inheritance is needed here?

The above tables can be also mapped without any fancy inheritance so question is why should we bother? The real reason for this hassle are business rules which are different for each type of customer (and potentially sales territory). To be more specific, there might be different rules for queuing deliveries, calculating discounts etc.

It makes sense to encapsulate those rules in a separate class for each type of consumer. This line of thinking leads us to the class diagram like this:


Customer is marked as a abstract class so it can't be instantiated. For simplicity I have added here only one method GetCurrentDiscount() which represents business logic specific for each type. Additionally StoreConsumer and IndividualConsumer classes have a Details property which "links" to two other tables - Store or Individual.

Inheritance Mapping

In general NHibernate supports three different inheritance mapping strategies. In this case I will use table per class hierarchy strategy. With Fluent NHibernate Customer class can be mapped like this:

   1:  public class CustomerMap : ClassMap<Customer>
   2:  {
   3:      public CustomerMap()
   4:      {
   5:          Id(x => x.Id).GeneratedBy.Native();
   6:  
   7:          Map(x => x.ModifiedDate).Not.Nullable();
   8:          Map(x => x.AccountNumber).ReadOnly().Unique();
   9:  
  10:          References(x => x.SalesTerritory, "TerritoryID");
  11:          
  12:          DiscriminateSubClassesOnColumn<string>("CustomerType")
  13:              .SubClass<IndividualCustomer>("I",
  14:                                            m => m.HasOne(x => x.Details)
  15:                                                     .Cascade.SaveUpdate()
  16:                                                     .FetchType.Join())
  17:              .SubClass<StoreCustomer>("S",
  18:                                       m => m.HasOne(x => x.Details)
  19:                                                .Cascade.SaveUpdate()
  20:                                                .FetchType.Join());
  21:      }
  22:  }
Let me explain what is going on up there:
  • Thanks to Id(...) and Map(...) methods we can tell Fluent NHibernate about identity column and other simple "data" columns. You can find more about basics here.
  • References(...) method is used to map many-to-one association with SalesTerritory table. In this case column name doesn't follow the convention therefore it has to be explicitly specified.
  • CustomerType column is used as a discriminator. "The discriminator column contains marker values that tell the persistence layer what subclass to instantiate for a particular row." CustomerType can have two values:
    • I for individual customer, in which case IndividualCustomer class will be instantiated or
    • S for store and for StoreCustomer class.
  • Both subclasses map one additional one-to-one association. Based on above mapping you can't see that actually each subclass "links" to the different table, it will be clear though when you check both classes:
   1:  public class IndividualCustomer : Customer
   2:  {
   3:      public virtual Individual Details { get; set; }
   4:      
   5:      public override double GetCurrentDiscount()
   6:      {
   7:          // some logic here
   8:      }
   9:  }

And StoreCustomer:

   1:  public class StoreCustomer : Customer
   2:  {
   3:      public virtual Store Details { get; set; }
   4:      
   5:      public override double GetCurrentDiscount()
   6:      {
   7:          // some logic here
   8:      }
   9:  }

Please note that Details property has different type in each case, in each case NHibernate will be able to figure out which class should be created and what data should be populated ... awesome!

One-to-one association

There is one additional feature worth mentioning - as it was stated earlier, ID from Customer table equals ID from Store (or Individual) table. How it can be mapped to make sure that inserts will work? I will explain it based on Individual class:

   1:  public class Individual
   2:  {
   3:      public virtual int Id { get; set; }
   4:      public virtual int Contact { get; set; }
   5:      public virtual string Demographics { get; set; }
   6:      public virtual DateTime ModifiedDate { get; set; }
   7:      public virtual IndividualCustomer Customer { get; set; }
   8:  }
Individual class is always associated with individual customer therefore we can explicitly say that property Customer is of type IndividualCustomer. Other properties are fairly straightforward.

Here is the mapping class:

   1:  public class InidividualMap : ClassMap<Individual>
   2:  {
   3:      public InidividualMap()
   4:      {
   5:          Id(x => x.Id, "CustomerID").GeneratedBy.Foreign("Customer");
   6:  
   7:          Map(x => x.Demographics);
   8:          Map(x => x.ModifiedDate);
   9:          Map(x => x.Contact, "ContactID");
  10:  
  11:          HasOne(x => x.Customer).Constrained();
  12:      }
  13:  }

Appropriate generator for identity column is the key here:
foreign - uses the identifier of another associated object. Usually used in conjunction with a one-to-one primary key association.
With above mapping foreign generator knows about the Customer and will use exactly the same ID. More about foreign generator and different types of one-to-one association you can find here.

Related articles:

Shout it

5 comments:

Ty said...

i just stumbled on this post.

Fantastic job!!! -- this is by far the best inheritance example i've ever seen.

thanks for sharing.

Ty said...

using a repository, how would i create a new individual customer?

thebitterend77 said...

Useful post. Can you post full source code, please?

Tom van Enckevort said...

Your blog post just helped me out solve a problem! Thanks for the clear explanation about how to use DiscriminateSubClassesOnColumn.

radzio said...

A very clarifying note.
It's been a long time since the posting and some things have changed. For instance the inline subclasses are obsolete. Now one should use SubclassMap instead.