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 :)).