1
votes

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

1

1 Answers

0
votes

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