Fluent NHibernate
Auto Mapping Type Conventions

Now lets look at how you can do some deeper customisations of the Auto Mapping tool.

We're going to use the same domain as before, but with a few extensions.

public class Product
{  
  public int Id { get; private set; }  
  public virtual string Name { get; set; }
  public virtual string Description { get; set; }
  public virtual decimal Price { get; set; }
  public virtual ReplenishmentDay ReplenishmentDay { get; set; }
}  

public class Shelf  
{  
  public virtual int Id { get; private set; }  
  public virtual IList<Product> Products { get; private set; }  
  public virtual string Description { get; set; }

  public Shelf()
  {
    Products = new List<Product>();
  }
}

public class ReplenishmentDay
{
  public static readonly ReplenishmentDay Monday = new ReplenishmentDay("mon");
  /* ... */
  public static readonly ReplenishmentDay Sunday = new ReplenishmentDay("sun");

  private string day;

  private ReplenishmentDay(string day)
  {
    this.day = day;
  }
}

We've extended our domain with a Description and a ReplenishmentDay for the Product; the replenishment day is represented by a type-safe enum (using the type-safe enum pattern). Also there's a Description against the Shelf too (not sure why you'd have a description of a shelf, but hey, that's customers for you). These changes are mapped against the following schema:

table Product (
  ProductId int identity primary key,
  Name varchar(250),
  Description varchar(2000),
  Price decimal,
  RepOn int, 
  Shelf_FK int foreign key
)

table Shelf (
  ShelfId int identity primary key,
  Description varchar(2000)
)

Now, if you've been following along you'll remember that we made all strings default to 250; and yet the new description columns are 2000 characters long. The customer has stipulated that all descriptions of anything in the domain will always be 2000 or less characters, so lets map that without affecting our other rule for strings.

.ConventionDiscovery.Setup(s =>
{
  s.Add<DescriptionTypeConvention>();
  s.Add<DefaultStringLengthConvention>();

  // other conventions
});

We're using the Fluent NHibernate's Conventions again, which allow you to override anything in the mappings before they're given to NHibernate. Baring in mind that our convention in this case is only for string properties that are called "Description", lets see how the DescriptionTypeConvention is declared.

public class DescriptionTypeConvention
  : IPropertyConvention
{
  public bool Accept(IProperty propertyMapping)
  {
    return propertyMapping.PropertyType == typeof(string) &&
      propertyMapping.Property.Name == "Description";
  }

  public void Apply(IProperty propertyMapping)
  {
    propertyMapping.WithLengthOf(2000);
  }
}

It's fairly expressive of what it does, but I'll cover it for completeness. The IPropertyConvention specifies two methods: bool Accept(IProperty) and void Apply(IProperty). Accept should be implemented to return true for properties that you want this convention to deal with; this can be handled in any way you want, you could check the name, or it's ancestry, but in our case we just check whether it's a string and if it has the correct name. Apply is where the bulk of the work happens; this method gets called for every property mapping that has a type that Accept returns true for. We've implemented Apply to alter the length of the property. Simple really. To learn more about conventions, see the Conventions page.

With a simple implementation like this, we're able to map every Description property (that's a string) so that it has a length of 2000, all with an addition of only one line to our auto mapping configuration.

IUserType support

The other alteration to our domain was the addition of the ReplenishmentDay. There were two interesting things to consider for this change. Firstly, it's stored in an int column, which obviously doesn't match our type; and secondly the column is called RepOn, which we mustn't change. We're going to utilise NHibernate's IUserType to handle this column.

For the sake of this example we're going to assume you've got an IUserType called ReplenishmentDayUserType, but as it's beyond the scope of this post I won't actually show the implementation as it can be quite lengthy. It's best to just assume that the IUserType reads an int from the database and can convert it to a ReplenishmentDay instance. There's a nice example of implementing IUserType on Jakob Andersen's blog.

So how do we tell Fluent NHibernate to use an IUserType instead of the specified type? Easy, with another convention, this time we'll use an IUserTypeConvention, or more specifically we'll derive from the convenience base-class UserTypeConvention that Fluent NHibernate supplies.

.ConventionDiscovery.Setup(s =>
{
  s.Add<DescriptionTypeConvention>();
  s.Add<ReplenishmentDayTypeConvention>();
  s.Add<DefaultStringLength>();

  // other conventions
});

Here's how our new ReplenishmentDayTypeConvention looks:

public class ReplenishmentDayTypeConvention
  : UserTypeConvention<ReplenishmentDayUserType>
{
  public override void Apply(IProperty propertyMapping)
  {
    base.Apply(propertyMapping);

    propertyMapping.ColumnName("RepOn");
  }
}

The actual setting of the user type is handled by the base Apply implementation, and if we weren't also setting the column name then we wouldn't need to override that member at all.

So that's it, with those conventions we're able to keep our standard rule that all strings should be 250 characters or less, unless they're a Description, then they can be 2000 or less. Replenishment days use our type-safe enum, but are persisted to an int in the database, which also has a custom column name.

What next? Auto Mapping Altering Entities