Dynamic Mapping with NHibernate

time to read 5 min | 852 words

A while ago I posted how to handle dynamic mapping with Active Record, it was incredibly easy to do, because Active Record has a lot of smarts internally, and output the XML, on top of which NHibernate adds quite a bit of convention over configuration as well. Doing the same using NHibernate directly is possible, but a bit long winded. Here is the sample code, which link all the Employee properties to the correct entity:

Configuration cfg = new Configuration()
    .AddAssembly(typeof (Employee).Assembly)
    .AddAssembly(typeof(ScheduledTask).Assembly);
Mappings mappings = cfg.CreateMappings();
foreach (PersistentClass persistentClass in mappings.Classes)
{
    if (persistentClass.MappedClass.GetProperty("Employee") == null)
        continue;
    Property prop = new Property();
    PersistentClass employeeClass = cfg.GetClassMapping(typeof (Employee));
    Table table = employeeClass.Table;
    ManyToOne value = new ManyToOne(table);
    value.ReferencedEntityName = typeof (Employee).FullName;
    Column column = new Column("Employee");
    value.AddColumn(column);
    prop.Value = value;
    prop.Name = "Employee";
    prop.PersistentClass = employeeClass;
    persistentClass.AddProperty(prop);
    persistentClass.Table.AddColumn(column);
    persistentClass.Table.CreateForeignKey("FK_EmployeeTo" + persistentClass.MappedClass.Name,
                                           new Column[] {column,}, typeof (Employee).FullName);
}
cfg.BuildSessionFactory();
new SchemaExport(cfg).Execute(true, true, false, true);

As you can see, there is a lot that needs to be done, we have to tell NHibernate a lot of things it would generally be able to figure out on its own. We can shove this to an extension method and get really nice syntax:

public static void MapManyToOne<TEntityInterface, TEntity>(this Configuration cfg)
{
    Mappings mappings = cfg.CreateMappings();
    foreach (PersistentClass persistentClass in mappings.Classes)
    {
        var propertyNames = new List<string>();
        foreach (PropertyInfo property in persistentClass.MappedClass.GetProperties())
        {
            if (property.PropertyType == typeof (TEntityInterface))
            {
                propertyNames.Add(property.Name);
            }
        }
        if (propertyNames.Count == 0)
            continue; 

        var prop = new Property();
        PersistentClass targetClass = cfg.GetClassMapping(typeof (TEntity)); 

        foreach (string propertyName in propertyNames)
        {
            Table table = targetClass.Table;
            var value = new ManyToOne(table);
            value.ReferencedEntityName = typeof (TEntity).FullName;
            var column = new Column(propertyName);
            value.AddColumn(column);
            prop.Value = value;
            prop.Name = propertyName;
            prop.PersistentClass = targetClass;
            persistentClass.AddProperty(prop);
            persistentClass.Table.AddColumn(column);
            string fkName = string.Format("FK_{0}To{1}", propertyName, persistentClass.MappedClass.Name);
            persistentClass.Table.CreateForeignKey(fkName,
                                                   new[] {column,}, typeof (TEntity).FullName);
        }
    }
}

Now we can use this with the following syntax:

cfg.MapManyToOne<IEmployee, Employee>();

Which is much nicer.