I'm trying to configure an entity that has custom/dynamic fields with by code mappings. I've based the following code from these two articles:
http://ayende.com/blog/4776/support-dynamic-fields-with-nhibernate-and-net-4-0 http://notherdev.blogspot.co.uk/2012/01/mapping-by-code-dynamic-component.html
The Customer entity is defined as follows:
public class Customer
{
public virtual Guid Id { get; protected set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual DateTime Created { get; set; }
public virtual IDictionary<string, object> Attributes { get; protected set; }
protected Customer()
{
this.Attributes = new Dictionary<string, object>();
}
}
and the mapping is defined as:
public class Mapping : ClassMapping<Customer>
{
public Mapping()
{
Table("Customers");
Id(x => x.Id, id =>
{
id.Column("Id");
id.Generator(Generators.GuidComb);
});
Property(x => x.FirstName);
Property(x => x.LastName);
Property(x => x.Created);
Join("CustomerAttributes", x =>
{
x.Table("CustomerAttributes");
x.Key(k => k.Column("CustomerId"));
x.Optional(false);
x.Fetch(FetchKind.Join);
x.Component("Attributes", new
{
HasChildren = 0
},
m =>
{
m.Property(p => p.HasChildren);
});
});
}
}
I think I'm close to getting it working but the problem I'm having is that when I'm retrieving a customer the SQL generated is invalid. It is looking for the HasChildren field on the Customers table instead of the CustomerAttributes table. The SQL its generating is this:
SELECT customer0_.Id as Id1_0_, customer0_.FirstName as FirstName1_0_, customer0_.LastName as LastName1_0_, customer0_.Created as Created1_0_, customer0_.HasChildren as HasChild5_1_0_ FROM Customers customer0_ inner join CustomerAttributes customer0_1_ on customer0_.Id=customer0_1_.CustomerId WHERE customer0_.Id=?
The schema for the database is as follows:
Any ideas on how I can get NHibernate to select the custom/dynamic fields from the correct table?
Having done some further research on this it looks like this could be the cause:
https://nhibernate.jira.com/browse/NH-3198
Also, dumping the XML generated we get:
Notice that the <dynamic-component> is NOT a sub element of the <join>.
Before this will be fixed, you can download the source, and make these changes to make your code working properly:
IJoinMapper
Instead of this:
public interface IJoinMapper : IJoinAttributesMapper
, ICollectionPropertiesContainerMapper
, IBasePlainPropertyContainerMapper { }
We would need it defined like this:
public interface IJoinMapper : IJoinAttributesMapper, IPropertyContainerMapper { }
We would need this new override
public class JoinCustomizer<TEntity> : PropertyContainerCustomizer<TEntity>, IJoinMapper<TEntity>
where TEntity : class
{
protected override void RegisterDynamicComponentMapping<TComponent>(Expression<Func<TEntity, System.Collections.IDictionary>> property
, Action<IDynamicComponentMapper<TComponent>> mapping)
{
MemberInfo member = TypeExtensions.DecodeMemberAccessExpression(property);
ExplicitDeclarationsHolder.AddAsPropertySplit(new SplitDefinition(typeof(TEntity), splitGroupId, member));
base.RegisterDynamicComponentMapping(property, mapping);
}
...
A method: MapSplitProperties
of the: ModelMaper
would need one more else if
statement
else if (modelInspector.IsDynamicComponent(member))
{
MapDynamicComponent(member, memberPath, propertyType, propertiesContainer);
}
This would be new method def:
void MapSplitProperties(System.Type propertiesContainerType, IEnumerable<MemberInfo> propertiesToMap, IJoinMapper propertiesContainer)
{
foreach (var property in propertiesToMap)
{
MemberInfo member = property;
System.Type propertyType = property.GetPropertyOrFieldType();
var memberPath = new PropertyPath(null, member);
if (modelInspector.IsProperty(member))
{
MapProperty(member, memberPath, propertiesContainer);
}
else if (modelInspector.IsAny(member))
{
MapAny(member, memberPath, propertiesContainer);
}
else if (modelInspector.IsManyToOne(property))
{
MapManyToOne(member, memberPath, propertiesContainer);
}
else if (modelInspector.IsSet(property))
{
MapSet(member, memberPath, propertyType, propertiesContainer, propertiesContainerType);
}
else if (modelInspector.IsDictionary(property))
{
MapDictionary(member, memberPath, propertyType, propertiesContainer, propertiesContainerType);
}
else if (modelInspector.IsArray(property))
{
throw new NotSupportedException();
}
else if (modelInspector.IsList(property))
{
MapList(member, memberPath, propertyType, propertiesContainer, propertiesContainerType);
}
else if (modelInspector.IsIdBag(property))
{
MapIdBag(member, memberPath, propertyType, propertiesContainer, propertiesContainerType);
}
else if (modelInspector.IsBag(property))
{
MapBag(member, memberPath, propertyType, propertiesContainer, propertiesContainerType);
}
else if (modelInspector.IsComponent(propertyType))
{
MapComponent(member, memberPath, propertyType, propertiesContainer, propertiesContainerType);
}
else if (modelInspector.IsDynamicComponent(member))
{
MapDynamicComponent(member, memberPath, propertyType, propertiesContainer);
}
else
{
MapProperty(member, memberPath, propertiesContainer);
}
}
}
These changes should the above issues. I also passed these lines to the NH issue log
Also created a pull request here: https://github.com/nhibernate/nhibernate-core/pull/366