Mapping by code

By now, you should have got a very good idea of what purpose the mappings serve. This knowledge will make understanding this section and the next very easy. At the beginning of the chapter, we discussed what is mapping by code and why it is built. So without much ado, let me get straight into the "mapping by code" feature of NH.

In mapping by code, mappings for each class can be declared in a separate and own class. It is also possible to declare the mappings in a single place by using the ModelMapper class. We would prefer to declare mappings for each entity in its own class. This class must inherit from another class provided by NHibernate called NHibernate.Mapping.ByCode.ClassMapping<T>. Whatever mappings you want to declare should be declared in the constructor of the class. Following code snippet shows how this looks for the Employee class:

using NHibernate.Mapping.ByCode;
using NHibernate.Mapping.ByCode.Conformist;
using Domain;

namespace Persistence.Mappings.ByCode
{
  public class EmployeeMappings : ClassMapping<Employee>
  {
    public EmployeeMappings()
    {
      //Declare mappings here
    }
  }
}

We declared a class called EmployeeMappings which inherits from ClassMapping<Employee>. The generic parameter Employee tells NHibernate that this class will hold the mappings for the Employee class. We then added a default constructor where in all our mappings would be added. That was simple, wasn't it? Next, we are going to look at how to declare different mappings that we learned in the XML mapping section. I will cover these mappings in more or less the same sequence as we covered earlier.

The API for mapping by code uses the same name as the nodes in XML mappings, making it very easy to construct and understand mappings. Like XML mappings, you are required to provide only the absolute minimum configuration and NHibernate would assume safe defaults for optional configuration. We have discussed important optional configurations already so I will not repeat them here. Let's get started then.

Identifier

Following is how to map identifier with minimal configuration:

Id(e => e.Id, mapper => mapper.Generator(Generators.HighLow));

Let me take a moment to explain what constitutes the preceding code. Id method is used to declare identifier mapping. The first parameter to this method is lambda expression to the property that acts as identifier of the class being mapped. The second parameter is delegate that takes NHibernate.Mapping.ByCode.IIdMapper as its parameter. NHibernate.Mapping.ByCode.IIdMapper has several methods defined on it that let you define optional configuration for the identifier. We have used one of these methods to specify that we want to use hilo generator for our identifier. You can see identifier mapping with some additional configuration specified in the following example:

Id(e => e.Id,
  mapper =>
  {
    mapper.Generator(Generators.HighLow);
    mapper.Column("Id");
    mapper.Length(10);
    mapper.UnsavedValue(0);
  });

Following image should give you an idea of how XML mappings compare to mapping by code:

Identifier

Property

Property mapping is quite simple. The only mandatory parameter it needs is lambda expression of the property being mapped. Following is how the EmployeeNumber property of the Employee class would be mapped.

Property(e => e.EmployeeNumber);

We probably do not need a side-by-side comparison of this mapping with XML. Following is same mapping with all optional parameters specified:

Property(e => e.EmployeeNumber,
  mapper =>
  {
    mapper.Column("employment_number");
    mapper.Type(NHibernateUtil.String);
    mapper.Length(10);
    mapper.NotNullable(true);
    mapper.Lazy(true);
    mapper.Unique(true);
  });

Association

As you know association mappings come in four different flavors. Let's look at them one by one.

One-to-many

One-to-many or collection mapping is declared using Set, Bag, List, Map, and so on. Syntactically they are all same so I would only use set as an example here. The collection mapping from Employee to Benefit that we wrote in the previous section can be written in code as follows:

Set(e => e.Benefits,
  mapper =>
  {
    mapper.Key(k => k.Column("Employee_Id"));
    mapper.Cascade(Cascade.All);
  },
  relation => relation.OneToMany(
  mapping => mapping.Class(typeof(Benefit))));

As you can see, the Set API takes more than two parameters. The first one is usual lambda expression to property being mapped, which happens to be Benefits in this case. Second one is a delegate accepting NHibernate.Mapping.ByCode.ISetPropertiesMapper<Employee,Benefit>. You can use the instance of ISetPropertiesMapper<Employee,Benefit> passed to this delegate to specify additional mapping configuration. Third parameter is another delegate accepting an instance of NHibernate.Mapping.ByCode.ICollectionElementRelation<Benefit> as its parameter. This instance can be used to specify details around one-to-many association between Employee and Benefit. This mapping is slightly complex than id and property mapping. Following picture comparing code mapping with XML mapping should help with understanding of this better:

One-to-many

Many-to-one

Many-to-one specifies the other end of one-to-many association. Only one parameter is mandatory on this mapping and following is how you declare this mapping in code:

ManyToOne(b => b.Employee, mapping =>
  mapping.Class(typeof(Employee)));

The example uses two parameters though only the first one is really required. Showing both parameters makes it easy in the comparison with XML mapping done as follows. First parameter is lambda to the property being mapped and second is delegate that lets you configure additional parameters for this mapping. This delegate accepts a parameter of type NHibernate.Mapping.ByCode.IManyToOneMapper which lets you declare more options for many-to-one association. Following is a side by side comparison of this mapping with many-to-one XML mapping:

Many-to-one

One-to-one

As we learned during XML mapping of one-to-one association, one-to-one association is really only one-to-many with a constraint that "many side" can only hold one item at max. Employee to Address one-to-one association would be declared using code mapping as shown next.

The Employee side can be represented as follows:

OneToOne(e => e.ResidentialAddress,
  mapper =>
  {
    mapper.Cascade(Cascade.Persist);
    mapper.PropertyReference(a => a.Employee);
  });

The Address side can be represented as shown next:

ManyToOne(a => a.Employee,
  mapper =>
  {
    mapper.Class(typeof (Employee));
    mapper.Column("Employee_Id");
    mapper.Unique(true);
  });

Nothing unusual here. But mappings accept lambda to property being mapped as first parameter and second is the delegate that specified additional mapping configuration. The delegate accepts a parameter of type NHibernate.Mapping.ByCode.IOneToOneMapper<Address> which you can use to declare additional mapping options for many-to-one association. Following is a side by side comparison of the preceding code with XML mapping:

One-to-one

Many-to-many

Similar to one-to-many, many-to-many is also declared using Set, Bag, List, Map, and so on. Let's take a look at set syntax for Employee to Community association:

Set(e => e.Communities,
  mapper =>
  {
    mapper.Key(k => k.Column("Employee_Id"));
    mapper.Table("Employee_Community");
    mapper.Cascade(Cascade.All);
  },
  relation => relation.ManyToMany(mtm =>
  {
    mtm.Class(typeof(Community));
    mtm.Column("Community_Id");
  }));

Again, this is not much different from one-to-many. The usual parameters are passed to the Set method. Last delegate parameter calls method ManyToMany and specifies the name of the foreign key column on the connecting table. Following is how this compares side by side to its XML equivalent:

Many-to-many

Component

API to map components takes two parameters. First is lambda to property referring to component class. In our case, this is the ResidentialAddress property on the Employee class. Second parameter is a delegate taking IComponentMapper<T> as its parameter. Here type T is the type of component class, which in our case is Address. IComponentMapper<T>has methods that let you map properties on the component class. Following is how you would map the Address component:

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

Inheritance

Mapping inheritance by code is quite simple. Mapping of base class is like mapping any other class where you inherit from ClassMapping<T>. Mapping of the derived classes is slightly different depending on which inheritance strategy you want. Unlike XML mapping where you wrote mappings for all classes in one XML file, here you would declare the mappings for each class in its own mapping class. Following is a code snippet for mapping the base class Benefit:

public class BenefitMappings : ClassMapping<Benefit>
{
  public BenefitMappings()
  {
    Id(b => b.Id, idmapper => idmapper.Generator(Generators.HighLow));
    Property(b => b.Name);
    Property(b => b.Description);
    ManyToOne(b => b.Employee, mapping => mapping.Class(typeof(Employee)));
  }
}

As you can see, this is like usual mapping for any other class. Let's see how derived classes are mapped in different strategies.

Table per class hierarchy

When mapping for "table per class hierarchy" by code, you need to ensure the following two things:

  • Mapping classes inherit from SubclassMapping<T> where T is the class being mapped
  • Value of discriminator column is declared by calling the DiscriminatorValue() method

Following is our benefits family of classes mapped using this strategy:

public class LeaveMappings : SubclassMapping<Leave>
{
  public LeaveMappings()
  {
    DiscriminatorValue("LVE");
    Property(l => l.Type);
    Property(l => l.AvailableEntitlement);
    Property(l => l.RemainingEntitlement);
  }
}
public class SeasonTicketLoanMappings :
SubclassMapping<SeasonTicketLoan>
{
  public SeasonTicketLoanMappings()
  {
    DiscriminatorValue("STL");
    Property(s => s.Amount);
    Property(s => s.MonthlyInstalment);
    Property(s => s.StartDate);
    Property(s => s.EndDate);
  }
}
public class SkillsEnhancementAllowanceMappings :
SubclassMapping<SkillsEnhancementAllowance>
{
  public SkillsEnhancementAllowanceMappings()
  {
    DiscriminatorValue("SEA");
    Property(s => s.Entitlement);
    Property(s => s.RemainingEntitlement);
  }
}

Table per subclass

In "table per subclass", each class gets mapped to a table in database. As mentioned earlier, base class is mapped in exactly the same way as "table per class hierarchy". The derived classes are mapped slightly differently. Their mapping class derives from JoinedSubclassMapping<Benefit> and also declares mapping of the shared primary key column. Following are mappings for all the three Benefit classes:

public class LeaveMappings : JoinedSubclassMapping<Leave>
{
  public LeaveMappings()
  {
    Key(k => k.Column("Id"));
    Property(l => l.Type);
    Property(l => l.AvailableEntitlement);
    Property(l => l.RemainingEntitlement);
  }
}

public class SeasonTicketLoanMappings : JoinedSubclassMapping<SeasonTicketLoan>
{
  public SeasonTicketLoanMappings()
  {
    Key(k => k.Column("Id"));
    Property(s => s.Amount);
    Property(s => s.MonthlyInstalment);
    Property(s => s.StartDate);
    Property(s => s.EndDate);
  }
}

public class SkillsEnhancementAllowanceMappings :
JoinedSubclassMapping<SkillsEnhancementAllowance>
{
  public SkillsEnhancementAllowanceMappings()
  {
    Key(k => k.Column("Id"));
    Property(s => s.Entitlement);
    Property(s => s.RemainingEntitlement);
  }
}

Note the declaration of primary key mapping using the method Key. This method accepts a delegate to which NHibernate.Mapping.ByCode.IKeyMapper<SkillsEnhancementAllowance> type is passed as parameter. IKeyMapper<SkillsEnhancementAllowance> is used to specify mapping of key column. There are several mapping configurations you can specify here but we have only specified the mandatory one, which is name of the key column.

Table per concrete class

There is only one thing you need to remember while mapping classes using the "table per concrete class" strategy. The class that declares the mappings for derived classes inherits from the UnionSubclassMapping<T> class. We have already covered how to map the base class. Following is how this mapping looks for our benefit classes:

public class LeaveMappings : UnionSubclassMapping<Leave>
{
  public LeaveMappings()
  {
    Property(l => l.Type);
    Property(l => l.AvailableEntitlement);
    Property(l => l.RemainingEntitlement);
  }
}

public class SeasonTicketLoanMappings :
UnionSubclassMapping<SeasonTicketLoan>
{
  public SeasonTicketLoanMappings()
  {
    Property(s => s.Amount);
    Property(s => s.MonthlyInstalment);
    Property(s => s.StartDate);
    Property(s => s.EndDate);
  }
}

public class SkillsEnhancementAllowanceMappings :
UnionSubclassMapping<SkillsEnhancementAllowance>
{
  public SkillsEnhancementAllowanceMappings()
  {
    Property(s => s.Entitlement);
    Property(s => s.RemainingEntitlement);
  }
}

Note that the only different thing we are doing here is that mapping classes inherit from UnionSubclassMapping<T> instead of usual ClassMapping<T>. Other than this, everything else is done in the usual way.

This concludes our discussion on mapping by code. Let's take a step back and look at complete mappings by code for our domain model.

Complete mappings by code for the employee benefits domain

So far we have seen snippets of mapping by code sprinkled across multiple sections. While that is useful in understanding one concept at a time, a complete picture is necessary to see how things fit together. Take time to go through the following code to get an idea of end-to-end mapping by code for our domain model.

The following is the code for the Employee domain:

public EmployeeMappings()
{
  Id(e => e.Id, mapper => mapper.Generator(Generators.HighLow));
  Property(e => e.EmployeeNumber);
  Property(e => e.Firstname);
  Property(e => e.Lastname);
  Property(e => e.EmailAddress);
  Property(e => e.DateOfBirth);
  Property(e => e.DateOfJoining);
  Property(e => e.IsAdmin);
  Property(e => e.Password);

  OneToOne(e => e.ResidentialAddress, mapper =>
  {
    mapper.Cascade(Cascade.Persist);
    mapper.PropertyReference(a => a.Employee);
  });

  Set(e => e.Benefits, mapper =>
  {
    mapper.Key(k => k.Column("Employee_Id"));
    mapper.Cascade(Cascade.All);
  },
  relation => relation.OneToMany(mapping =>
  mapping.Class(typeof(Benefit))));

  Set(e => e.Communities,
  mapper =>
  {
    mapper.Key(k => k.Column("Employee_Id"));
    mapper.Table("Employee_Community");
    mapper.Cascade(Cascade.All);
  },
  relation => relation.ManyToMany(mtm =>
  {
    mtm.Class(typeof(Community));
    mtm.Column("Community_Id");
  }));
}

The following is the code for the Address domain:

public class AddressMappings : ClassMapping<Address>
{
  public AddressMappings()
  {
    Id(a => a.Id, mapper => mapper.Generator(Generators.HighLow));
    Property(a => a.AddressLine1);
    Property(a => a.AddressLine2);
    Property(a => a.Postcode);
    Property(a => a.City);
    Property(a => a.Country);
    ManyToOne(a => a.Employee, mapper =>
    {
      mapper.Class(typeof (Employee));
      mapper.Column("Employee_Id");
      mapper.Unique(true);
    });
  }
}

The following is the code for the Community domain:

public class CommunityMappings : ClassMapping<Community>
{
  public CommunityMappings()
  {
    Id(c => c.Id, idmapper =>
    idmapper.Generator(Generators.HighLow));
    Property(c => c.Name);
    Property(c => c.Description);
    Set(c => c.Members,
    mapper =>
    {
      mapper.Key(k => k.Column("Community_Id"));
      mapper.Table("Employee_Community");
      mapper.Cascade(Cascade.All);
      mapper.Inverse(true);
    },
    relation => relation.ManyToMany(mapper =>
    {
      mapper.Class(typeof (Employee));
      mapper.Column("Employee_Id");
    }));
  }
}

The following is the code for the Benefit domain:

public class BenefitMappings : ClassMapping<Benefit>
{
  public BenefitMappings()
  {
    Id(b => b.Id, idmapper =>
    idmapper.Generator(Generators.HighLow));
    Property(b => b.Name);
    Property(b => b.Description);
    ManyToOne(b => b.Employee, mapping =>
    mapping.Class(typeof(Employee)));
  }
}

The following is the code for the Leave domain:

public class LeaveMappings : JoinedSubclassMapping<Leave>
{
  public LeaveMappings()
  {
    Key(k => k.Column("Id"));
    Property(l => l.Type);
    Property(l => l.AvailableEntitlement);
    Property(l => l.RemainingEntitlement);
  }
}

The following is the code for the SkillsEnhancementAllowance domain:

public class SkillsEnhancementAllowanceMappings :
JoinedSubclassMapping<SkillsEnhancementAllowance>
{
  public SkillsEnhancementAllowanceMappings()
  {
    Key(k => k.Column("Id"));
    Property(s => s.Entitlement);
    Property(s => s.RemainingEntitlement);
  }
}

The following is the code for the SeasonTicketLoan domain:

public class SeasonTicketLoanMappings :
JoinedSubclassMapping<SeasonTicketLoan>
{
  public SeasonTicketLoanMappings()
  {
    Key(k => k.Column("Id"));
    Property(s => s.Amount);
    Property(s => s.MonthlyInstalment);
    Property(s => s.StartDate);
    Property(s => s.EndDate);
  }
}
..................Content has been hidden....................

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