Search code examples
nhibernatenhibernate-mapping-by-code

Dynamic fields and by code mapping


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:

schema diagram

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:

enter image description here

Notice that the <dynamic-component> is NOT a sub element of the <join>.


Solution

  • Before this will be fixed, you can download the source, and make these changes to make your code working properly:

    1) IJoinMapper

    Instead of this:

    public interface IJoinMapper : IJoinAttributesMapper
          , ICollectionPropertiesContainerMapper
          , IBasePlainPropertyContainerMapper { }
    

    We would need it defined like this:

    public interface IJoinMapper : IJoinAttributesMapper, IPropertyContainerMapper { }
    

    2) JoinCustomizer

    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);
        }
       ...
    

    3) ModelMapper

    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