Fluent mapping a.k.a. Fluent NHibernate

If you have liked mapping by code and are excited about being able to declare mappings programmatically, then I am sure you would fall in love with fluent mappings offered by Fluent NHibernate (FNH). As far as I can tell, fluent mapping is the most widely used technique of mapping in NHibernate world and we will see why everyone likes it so much.

FNH scans your code based on mappings, processes them, and comes up with appropriate XML mappings. All this process is transparent to you as developer. But it is useful to remember that all FNH is doing is transforming code-based mappings into XML mappings.

Before we start writing fluent mappings for our classes, let me tell you that FNH, for some reasons, chose to name their APIs differently than their XML or mapping by code counterparts. For example, property mapping in FNH is named Map. This could be a cause of confusion if you are new to FNH but I'm sure your love for FNH would outweigh the confusion and you would soon get used it. Let's get started then.

We will take a slightly different approach to learning fluent mappings. Instead of going through how to map different types of attributes individually, I am going to show you complete mappings for classes in our domain model. We will then look at individual fluent mappings declared for each class. Let's start with Employee:

using Domain;
using FluentNHibernate.Mapping;

namespace Persistence.Mappings.Fluent
{
  public class EmployeeMappings : ClassMap<Employee>
  {
    public EmployeeMappings()
    {
      Id(e => e.Id).GeneratedBy.HiLo("1000");
      Map(e => e.EmployeeNumber);
      Map(e => e.Firstname);
      Map(e => e.Lastname);
      Map(e => e.EmailAddress);
      Map(e => e.DateOfBirth);
      Map(e => e.DateOfJoining);
      Map(e => e.IsAdmin);
      Map(e => e.Password);

      HasOne(e => e.ResidentialAddress).Cascade.All().PropertyRef(a => a.Employee);
      HasMany(e => e.Benefits).Cascade.AllDeleteOrphan();
      HasManyToMany(e => e.Communities)
                         .Table("Employee_Community")
                         .ParentKeyColumn("Employee")
                         .ChildKeyColumn("Community")
                         .Cascade.AllDeleteOrphan();
    }

  }
}

Let's see what is going on here:

  • The class that holds mappings inherits from FluentNHibernate.Mapping.ClassMap<T> where T is the class being mapped.
  • Mappings are declared in the default constructor of this class using helper methods available from base class.
  • Id method is used to map the identifier. This takes lambda to identifier property on the class. Method chaining is used to provide additional method on top of the Id method. These additional methods can be used to declare optional mapping configuration. In our example, we have used the GeneratedBy.HiLo("1000") method to configure a hilo identifier generator for our mapping. In Visual Studio, if you put a dot next to GeneratedBy, IntelliSense will list all the available identifier generators for you.
  • Map method is used to map properties on the class. The only mandatory parameter this takes is the lambda to property being mapped.
  • HasOne method maps a one-to-one association. The parameter passed to it is lambda to the property being mapped. Chained methods are available to configure optional mappings. We have used Cascade.All() to declare cascade settings and PropertyRef() to specify value for the property-ref attribute of XML one-to-one mapping. Note how PropertyRef takes a lambda to a property on the other end of the association and makes our lives easier.
  • HasMany method is used to map one-to-many relationship from the Employee to Benefit class. As you can see, the only mandatory parameter is lambda to the Benefits collection property on the Employee class. FNH chooses appropriate XML element for you out of Set, Map, Bag, List, and so on.
  • HasManyToMany declares a many-to-many mapping. Are you not surprised by the simplicity? First parameter, lambda to property being mapped, is usual. You only have to specify the name of the connecting table via chained method Table() method and foreign keys from connecting table to both tables on many end via ParentKeyColumn() and ChildKeyColumn() methods.

Let's look at mapping of the Community class next to see how the other side of many-to-many is mapped. Following is complete fluent mapping for the Community class:

public class CommunityMappings : ClassMap<Community>
{
  public CommunityMappings()
  {
    Id(c => c.Id).GeneratedBy.HiLo("1000");
    Map(c => c.Name);
    Map(c => c.Description);
    HasManyToMany(c => c.Members)
    .Table("Employee_Community")
    .ParentKeyColumn("Community_Id")
    .ChildKeyColumn("Employee_Id")
    .Cascade.AllDeleteOrphan();
  }
}

There is nothing new in here from mappings of the Employee class except a minor detail. ParentKeyColumn is the passed name of foreign key column to the Community table and ChildKeyColumn is the passed name of the foreign key column to the Employee table. This was the other way round in EmployeeMappings for obvious reasons.

Mappings of the Address class are not vastly different as well, again except for mapping of many-to-one Employee association. Let's take a look:

public class AddressMappings : ClassMap<Address>
{
  public AddressMappings()
  {
    Id(a => a.Id).GeneratedBy.HiLo("1000");
    Map(a => a.AddressLine1);
    Map(a => a.AddressLine2);
    Map(a => a.City);
    Map(a => a.Postcode);
    Map(a => a.Country);
    References(a => a.Employee);
  }
}

Again, most of the things are same. Many-to-one association to the Employee class is mapped using the method References. If you ignore the unusual choice of name to represent many-to-one association, the mapping is quite succinct.

We have covered most of the mappings in our domain model, except for inheritance mappings which we would cover next.

Inheritance mappings

FNH supports all three ways of mapping classes inheriting from other entity. The only limitation is that you cannot mix different inheritance mapping strategies. Other than that, FNH offers quite a succinct and intuitive API for mapping inheritance.

Table per class hierarchy

By now, you know what "table per class hierarchy" means, hence I would directly show you the mapping code for this in FNH:

public class BenefitMappings : ClassMap<Benefit>
{
  public BenefitMappings()
  {
    Id(b => b.Id).GeneratedBy.HiLo("1000");
    Map(b => b.Name);
    Map(b => b.Description);
    References(b => b.Employee);
  }
}

public class LeaveMappings : SubclassMap<Leave>
{
  public LeaveMappings()
  {
    DiscriminatorValue("LVE");
    Map(l => l.AvailableEntitlement);
    Map(l => l.RemainingEntitlement);
    Map(l => l.Type);
  }
}

public class SeasonTicketLoanMappings : SubclassMap<SeasonTicketLoan>
{
  public SeasonTicketLoanMappings()
  {
    DiscriminatorValue("STL");
    Map(s => s.Amount);
    Map(s => s.MonthlyInstalment);
    Map(s => s.StartDate);
    Map(s => s.EndDate);
  }
}

public class SkillsEnhancementAllowanceMappings :
SubclassMap<SkillsEnhancementAllowance>
{
  public SkillsEnhancementAllowanceMappings()
  {
    DiscriminatorValue("SEA");
    Map(s => s.Entitlement);
    Map(s => s.RemainingEntitlement);
  }
}

There are two new things in the previous mappings:

  • Mapping classes for the derived entities inherit from SubclassMap<T> instead of ClassMap<T>. That is how you tell FNH that you are mapping inheritance.
  • Mappings of derived classes calls method DiscriminatorValue(). This serves two purposes. First, it tells FNH that this mapping should be a table per class hierarchy mapping, and second it tells what discriminator value NHibernate should use while mapping the class.

If you are thinking that this was simple then I can only advise you to keep reading.

Table per concrete class

In order to map a family of classes using the "table per concrete class" strategy, you need to do two things different from the "table per class hierarchy" strategy.

Remove the declaration of discriminator column from mappings of derived classes.

In the mapping of base class, tell FNH that this family of classes should be mapped using the "table per concrete class" strategy. The following code sample shows you how to do that.

If you remove the declaration of the discriminator column from mappings of derived classes then all you are left with is mapping of properties on the class itself. You have seen this code already in the "table per class hierarchy" strategy. To keep the text of the chapter brief, I will not present the mapping code for derived class. Following is how the Benefit class will be mapped:

public class BenefitMappings : ClassMap<Benefit>
{
  public BenefitMappings()
  {
    Id(b => b.Id).GeneratedBy.HiLo("1000");
    Map(b => b.Name);
    Map(b => b.Description);
    References(b => b.Employee);
    UseUnionSubclassForInheritanceMapping();
  }
}

Note the call to UseUnionSubclassForInheritanceMapping(). That is the only thing you need in order to tell FNH that this is a "table per concrete class" mapping.

If you are thinking that this was even simpler, then I can only tell you to keep reading.

Table per subclass

This is the most simple of the inheritance mappings in FNH. If you do not declare discriminator column and do not tell FNH to use the union-subclass mapping (or the "table per concrete class" strategy), then FNH by default maps as "table per subclass". So if I could omit the mapping of properties on our Benefit classes, then following is how a "table per subclass" mapping would look:

public class BenefitMappings : ClassMap<Benefit>
{
}

public class LeaveMappings : SubclassMap<Leave>
{
}

public class SeasonTicketLoanMappings : SubclassMap<SeasonTicketLoan>
{
}

public class SkillsEnhancementAllowanceMappings :
SubclassMap<SkillsEnhancementAllowance>
{
}

I don't think it can get any simpler than this.

Component mapping

Mapping components in FNH is similar to one in mapping by code. FNH has a method named Component, which like mapping by code takes two parameters. First one is lambda expression to property being mapped. Second is a delegate accepting parameter of type ComponentPart<T> where T is of component class type. For our domain model, it would be ComponentPart<Address>. Following is FNH's component mapping for the ResidentailAddress property on the Employee class:

Component(e => e.ResidentialAddress,
               mapper =>
               {
               mapper.Map(a => a.AddressLine1);
               mapper.Map(a => a.AddressLine2);
               mapper.Map(a => a.City);
               mapper.Map(a => a.Postcode);
               mapper.Map(a => a.Country);
               });

This code is exactly same as it's mapping by code counterpart.

We have finished fluent mappings unbelievably faster. That is the nature of it. As with XML mappings, we have only scratched the surface of fluent mappings. For now, I have to pause and reflect on all three mechanisms we have learned.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.181.57