Fluent NHibernate: How to Map M:N many-to-many with composite keys on both sides

99封情书 提交于 2019-12-03 10:13:41
Lachlan Roche

If FluentNHibernate is currently unable to map this, then you can map it with a hbm.xml file.

I also used a component for the composite id of both classes. This makes the identity separate from the entity, allowing session.Get<M2M2ParentA>( new M2M2Id( 1, 2 )). See this answer for a discussion of the 3 ways to represent composite-id (it is the same for NHibernate and Hibernate).

<class name="M2M2ParentA" table="M2M2ParentA">
    <composite-id name="Id" class="M2M2Id">
        <key-property name="Id1" />
        <key-property name="Id2" />
    </composite-id>
    <bag name="BList" table="M2M2Link" lazy="false" fetch="join" >
        <key>
            <column name="M2M2ParentAId1" />
            <column name="M2M2ParentAId2" />
        </key>
        <many-to-many class="M2M2ParentB" >
            <column name="M2M2ParentBId1" />
            <column name="M2M2ParentBId2" />
        </many-to-many>
    </bag>
</class>

<class name="M2M2ParentB" table="M2M2ParentB">
    <composite-id name="Id" class="M2M2Id">
        <key-property name="Id1" />
        <key-property name="Id2" />
    </composite-id>
    <bag name="AList" table="M2M2Link" lazy="false" fetch="join" inverse="true">
        <key>
            <column name="M2M2ParentBId1" />
            <column name="M2M2ParentBId2" />
        </key>
        <many-to-many class="M2M2ParentA" >
            <column name="M2M2ParentAId1" />
            <column name="M2M2ParentAId2" />
        </many-to-many>
    </bag>
</class>

And my version of your classes.

public class M2M2ParentA
{
    public M2M2ParentA()
    {
        BList = new List<M2M2ParentB>();
    }
    public virtual M2M2Id Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<M2M2ParentB> BList { get; set; }
}

public class M2M2ParentB
{
    public M2M2ParentB()
    {
        AList = new List<M2M2ParentA>();
    }
    public virtual M2M2Id Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<M2M2ParentA> AList { get; set; }
}

public class M2M2Id
{
    public M2M2Id() {}
    public M2M2Id( int id1, int id2 )
    {
        Id1 = id1;
        Id2 = id2;
    }
    public virtual int Id1 { get; set; }
    public virtual int Id2 { get; set; }
    public override int GetHashCode()
    {
        return Id1.GetHashCode() + Id2.GetHashCode();
    }
    public override bool Equals( object obj )
    {
        M2M2Id other = obj as M2M2Id;
        return other != null && Id1 == other.Id1 && Id2 == other.Id2;
    }
}

The short answer to your question is that Fluent can't handle that situation yet, and you should map it with a .hbm.xml file.

The long answer is that Fluent CAN actually do it if the composite keys of your 2 parent tables are instead made up of foreign keys to 4 separate grandparent tables (GPA1,GPA2,GPB1,GPB2 - in the example below). In that case, the mapping could look something like this... (but it IS a bit hackey)

//OBJECTS
public class GPA1
{
    public virtual long ID {get;set;}
}
public class GPA2
{
    public virtual long ID { get; set; }
}
public class GPB1
{
    public virtual long ID { get; set; }
}
public class GPB2
{
    public virtual long ID { get; set; }
}
public class M2M2ParentA
{
    public virtual GPA1 ID1A { get; set; }
    public virtual GPA2 ID2A { get; set; }
}
public class M2M2ParentB
{
    public virtual GPB1 ID1B { get; set; }
    public virtual GPB2 ID2B { get; set; }
}
public class M2M2Link
{
    public virtual M2M2ParentA LinkA { get; set; }
    public virtual M2M2ParentB LinkB { get; set; }

    public virtual GPA1 ID1A
    {
        get { return LinkA.ID1A; }
        set { LinkA.ID1A = value; }
    }
    public virtual GPA2 ID2A
    {
        get { return LinkA.ID2A; }
        set { LinkA.ID2A = value; }
    }
    public virtual GPB1 ID1B
    {
        get { return LinkB.ID1B; }
        set { LinkB.ID1B = value; }
    }
    public virtual GPB2 ID2B
    {
        get { return LinkB.ID2B; }
        set { LinkB.ID2B = value; }
    }
}
//FLUENT MAPPINGS
public class GPA1Map : ClassMap<GPA1>
{
    public GPA1Map()
    {
        Table("GPA1");

        Id(x => x.ID, "id_column");
    }
}
public class GPA2Map : ClassMap<GPA2>
{
    public GPA2Map()
    {
        Table("GPA1");

        Id(x => x.ID, "id_column");
    }
}
public class GPB1Map : ClassMap<GPB1>
{
    public GPB1Map()
    {
        Table("GPA1");

        Id(x => x.ID, "id_column");
    }
}
public class GPB2Map : ClassMap<GPB2>
{
    public GPB2Map()
    {
        Table("GPA1");

        Id(x => x.ID, "id_column");
    }
}
public class M2M2ParentAMap : ClassMap<M2M2ParentA>
{
    public M2M2ParentAMap()
    {
        Table("M2M2ParentA");

        CompositeId()
            .KeyReference(x => x.ID1A, "M2M2ParentAId1")
            .KeyReference(x => x.ID1A, "M2M2ParentAId2");
    }
}
public class M2M2ParentBMap : ClassMap<M2M2ParentB>
{
    public M2M2ParentBMap()
    {
        Table("M2M2ParentB");

        CompositeId()
            .KeyReference(x => x.ID1B, "M2M2ParentBId1")
            .KeyReference(x => x.ID1B, "M2M2ParentBId2");
    }
}
public class M2M2LinkMap : ClassMap<M2M2Link>
{
    public M2M2LinkMap()
    {
        Table("M2M2Link");

        CompositeId()
            .KeyReference(x => x.ID1A, "M2M2ParentA_Id1")
            .KeyReference(x => x.ID1B, "M2M2ParentA_Id2")
            .KeyReference(x => x.ID2A, "M2M2ParentB_Id1")
            .KeyReference(x => x.ID2B, "M2M2ParentB_Id2");
    }
}

There should be HasMany<> relationships in each of those mapping classes, but I got lazy. If you go the route of including a .hbm.xml file, I'd recommend using the .ExportTo("c:\") function when you configure your ISessionFactory and you can just edit the hbm file that fluent puts out. It's a great starting point.

HTH, -Danno

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!