Fluent NHibernate Auto Persistence Model and composite element lists

If you're using Fluent NHibernate AutoPersistenceModel and have struggled when you want to define a one to many relationship as a composite-element then this blog may help you.

When using the AutoPersistenceModel the mapping generated for a relationship by default looks like the following:

   1: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-access="">
   2:   <class name="Parent" xmlns="urn:nhibernate-mapping-2.2">
   3:     <id name="Id" type="Int32" column="Id">
   4:       <generator class="identity" />
   5:     </id>
   6:     <property name="Property" type="String">
   7:       <column name="Property" />
   8:     </property>
   9:     <set name="Children">
  10:       <key column="ParentID" />
  11:       <one-to-many class="child" />
  12:     </set>
  13:   </class>
  14: </hibernate-mapping>

For clarity the class would look something like

   1: public class Parent
   2: {
   3:     public string Property {get; set;}
   4:     public ISet<Child> Children {get; set;}
   5: }
   6:  
   7: public class Child
   8: {
   9:     public string ChildProperty {get; set;}
  10: }

Now - what if you want the Child to be a composite element?  When hand writing the mapping you'd just change it to something like below:

   1: <set name="Children" cascade="all-delete-orphan" lazy="false">
   2:   <key column="ParentID" />
   3:   <composite-element class="Child">
   4:     <property name="ChildProperty">
   5:       <column name="ChildProperty" />
   6:     </property>
   7:   </composite-element>
   8: </set>

If it wasn't a list you could use WithSetup as shown below.

   1: AutoPersistenceModel.MapEntitiesFromAssemblyOf<Parent>()
   2:    .WithSetup(e => e.IsComponentType = t => t == typeof (Child));

However, the code above doesn't work for a list.

You can use the fluent API to make a composite list which the code below demonstrates:

   1: mapping.ForTypesThatDeriveFrom<Parent>(m => m.HasMany(p => p.Children).Component(comp =>
   2:  {
   3:     comp.Map(child => child.ChildProperty);
   4:  }));

My bug with this is it's already generated the mapping for Child, why do I need to specify it again when saying it's a component.  It would be nice if the WithSetup let me do it but it doesn't so I've had to write my own code to do the same thing...

I'm working on a small project with a very simple model and AutoPersistenceModel does 90% of what I need - the last 10% is that I want some of my types to be composites (at the moment anyway)...  I want to try and keep it nice and clean, i.e. don't mix manual configuration with auto configuration.  I much prefer the NHibernate XML configuration to using the Fluent API as I think it's easier to read (albeit a bit less refactor friendly).  So, I was at the point of throwing away AutoPersistenceModel and hand cranking out my XML.  But I thought I'd try and make something that could do what I wanted.  My scenario isn't that complicated, I have no idea if this code will work well for more complicated models with composite of composites but it's a start (my model may evolve to something like this so never know I may need to solve it, or use mapping xml).

So with my new extension method I can now do this:

   1: var referenceMapping = AutoPersistenceModel.MapEntitiesFromAssemblyOf<Parent>();
   2: var mapping = AutoPersistenceModel.MapEntitiesFromAssemblyOf<Parent>()
   3:    .ReconfigureToComposite<Parent, Child>(referenceMapping, "Children");

I don't have to redefine the mapping for Child.

The extension method looks like this:

public static class FluentNHibernateExtensionMethods
   {
      public static AutoPersistenceModel ReconfigureToComposite<TRoot, TChild>(this AutoPersistenceModel mapping, AutoPersistenceModel referenceMapping, string propertyName)
      {
         var mappings = new List<Expression<Func<TChild, object>>>();
 
         var childMapping = referenceMapping.FindMapping(typeof(TChild));
         foreach (var property in childMapping.GetClassMapping().Properties)
         {
            var param = Expression.Parameter(typeof(TChild), "p");
            var prop = Expression.Property(param, property.PropertyInfo);
            mappings.Add(Expression.Lambda<Func<TChild, object>>(prop, param));
         }
 
         var hasManyParam = Expression.Parameter(typeof (TRoot), "r");
         var hasManyExpression = Expression.Lambda<Func<TRoot, object>>(Expression.Property(hasManyParam, propertyName), hasManyParam);
 
         mapping.ForTypesThatDeriveFrom<TRoot>(
            m => m.HasMany<TChild>(hasManyExpression).Component(c =>
               {
                  foreach (var map in mappings)
                  {
                     c.Map(map);
                  }
               })
               .Cascade.AllDeleteOrphan()
               .Not.LazyLoad());
 
            return mapping;
      }
   }

Hopefully someone will find this useful.  If anyone knows an easier way to achieve the same thing then please let me know (maybe I missed something or maybe they'll release a new feature soon :)).

Add Comment Filed Under [ NHibernate ]

Comments

No comments posted yet.

Leave Your Comment

Title*
Name*
Email (never displayed)
 (will show your gravatar)
Url
Comment*

Please add 6 and 6 and type the answer here:

Preview Your Comment.