fluent-nhibernate icon indicating copy to clipboard operation
fluent-nhibernate copied to clipboard

Dictionary mapping maps to interfaces instead of implemention

Open fennekit opened this issue 12 years ago • 7 comments

I have a IDictionary<IA, IB> dictionary in my class. I try to map this with FluentNHibernate but it creates a wrong mapping. The application shows that it generates a map using IA and IB instead of classes A and B. This is also clearly visible from the exported *.hbm.xml files. I am using Fluent-nhibernate 1.3.

The following code shows the problem:

using FluentNHibernate.Cfg;
using FluentNHibernate.Mapping;
using NHibernate;
using NHibernate.Cfg;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    public interface IA
    {
        int Id { get; set; }
        string PropA { get; set; }
    }

    public interface IB
    {
        int Id { get; set; }
        string PropB { get; set; }
    }
    public interface IC
    {
        int Id { get; set; }
        IDictionary<IA, IB> Dictionary { get; set; }
    }
    public class A : IA
    {
        public virtual int Id { get; set;}
        public string PropA { get; set; }
    }
    public class B : IB
    {
        public virtual int Id { get; set; }
        public string PropB { get; set; }
    }

    public class C : IC
    {
        public virtual int Id { get; set; }
        public virtual IDictionary<IA, IB> Dictionary { get; set; }
    }

    public class AMapper : ClassMap<A>
    {
        public AMapper()
        {
            Id(x => x.Id).GeneratedBy.Identity().Column("id");
            Map(x => x.PropA).Column("propa");
        }
    }
    public class BMapper : ClassMap<B>
    {
        public BMapper()
        {     
            Id(x => x.Id).GeneratedBy.Identity().Column("id");
            Map(x => x.PropB).Column("propa");
        }
    }
    public class CMapper : ClassMap<C>
    {  
        public CMapper()
        {     
            Id(x => x.Id).GeneratedBy.Identity().Column("id");
            HasMany<C>(x => x.Dictionary).Table("dict_table").AsEntityMap("C_id").KeyColumn("B_id");
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            Configuration conf = new Configuration().Configure();
            //configuration.AddAssembly(typeof(Program).Assembly);

            var nhConfig = Fluently.Configure(conf)
            .Mappings(mapping => 
                {
                    mapping.FluentMappings.AddFromAssembly(typeof(A).Assembly);
                    mapping.FluentMappings.ExportTo(@"c:\temp\");
                })
                .ExposeConfiguration(cfg =>{});

            Configuration configuration = nhConfig.BuildConfiguration();
            ISessionFactory sessionFactory = configuration.BuildSessionFactory();
            sessionFactory.OpenSession();


        }
    }
}

fennekit avatar Jul 24 '13 20:07 fennekit

But you declared IDictionary of IA, IB. Fluent doesn't know that only implementations of IA and IB are classes A and B, respectively, neither does he know that you want concrete type Dictionary to be used for the interface. Also, didn't you mean to write HasMany[B] or HasMany[IB] in the mapping?

chester89 avatar Jul 24 '13 20:07 chester89

It is possible to do the same with other 'normal' relations. Using the classic xml files I can get this working. I am currently on holiday. End of next week I can provide some examples.

It may be not a bug, but an incomplete API.

fennekit avatar Aug 17 '13 20:08 fennekit

Please provide examples

chester89 avatar Aug 18 '13 06:08 chester89

If I have a reference to an interface in my domain object

public virtual IModel Model { get; set; }

I can specify in my mapping

References<Model>(x => x.Model).Column("model_id"); 

and this works (since Model implements IModel). I need something similar for Dictionaries.

The implementation of the dictionary is as follows in Angle.cs

public virtual IDictionary<IUser, IUserAngle> AngleUsers
        {
            get
            {
                return _angleUsers;
            }
            set
            {
                _angleUsers = value;
            }
        }

The mapping in AngleMap.cs is

HasMany<UserAngle>(x => x.AngleUsers).Table("UserAngle").AsEntityMap("UserId").KeyColumn("AngleId").Cascade.All();

the mapping generated for this relation by FluentNhibernate is the following (intermediate) Angle.hbm.xml

<map cascade="all" name="AngleUsers" table="UserAngle">
      <key>
        <column name="AngleId" />
      </key>
      <index-many-to-many class="EveryAngle.Edgar.Domain.User.IUser, EveryAngle.Edgar.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
        <column name="UserId" />
      </index-many-to-many>
      <one-to-many class="EveryAngle.Edgar.Domain.User.IUserAngle, EveryAngle.Edgar.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </map>

This should be (this works, tried it by doing the mapping with the Angles.hbm.xml) (Note the use of the class instead of the Interface)

<map cascade="all" name="AngleUsers" table="UserAngle">
      <key>
        <column name="AngleId" />
      </key>
      <index-many-to-many class="EveryAngle.Edgar.Domain.User.User, EveryAngle.Edgar.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
        <column name="UserId" />
      </index-many-to-many>
      <one-to-many class="EveryAngle.Edgar.Domain.User.UserAngle, EveryAngle.Edgar.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
    </map>

In short the API does not have a way to specify the classes to use for the dictionary (in my opinion).

fennekit avatar Aug 22 '13 08:08 fennekit

Ok, that's more like it, let's see what I can do to help.

2013/8/22 Fennek IT [email protected]

If I have a reference to an interface in my domain object

public virtual IModel Model { get; set; }

I can specify in my mapping

References<Model>(x => x.Model).Column("model_id");

and this works (since Model implements IModel). I need something similar for Dictionaries.

The implementation of the dictionary is as follows in Angle.cs

public virtual IDictionary<IUser, IUserAngle> AngleUsers { get { return _angleUsers; } set { _angleUsers = value; } }

The mapping in AngleMap.cs is

HasMany<UserAngle>(x => x.AngleUsers).Table("UserAngle").AsEntityMap("UserId").KeyColumn("AngleId").Cascade.All();

the mapping generated for this relation by FluentNhibernate is the following (intermediate) Angle.hbm.xml

This should be (this works, tried it by doing the mapping with the Angles.hbm.xml) (Note the use of the class instead of the Interface)

In short the API does not have a way to specify the classes to use for the dictionary (in my optionion).

Reply to this email directly or view it on GitHubhttps://github.com/jagregory/fluent-nhibernate/issues/234#issuecomment-23075367 .

ó Õ×ÁÖÅÎÉÅÍ, þÅÒÍ£ÎÎÏ× çÌÅÂ, ÔÅÌ. (916) 314-9324

chester89 avatar Aug 22 '13 10:08 chester89

Hi, would adding an AsEntityMap function to ManyToManyPart.cs help?

        public ManyToManyPart<TChild> AsEntityMap(Type indexType, string indexColumn, Type typeOfValue, string valueColumn)
        {
            return AsMap(null).AsTernaryAssociation(indexType, indexColumn,typeOfValue, valueColumn);
        }

This would allow me to specify the necessary types.

fennekit avatar Aug 26 '13 11:08 fennekit

Create a pull request for this issue.

fennekit avatar Aug 26 '13 19:08 fennekit