Dynamic Mapping with NHibernate
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.