Search code examples
c#hibernatenhibernatenhibernate-mappinghibernate-mapping

How to map a many-to-many-to-many ternary relationship in NHibernate?


Trying to make a many-to-many-to-many association.

What I have so far is this:

namespace com.example // Assembly = com.example
{

    public class Foo
    {
        public virtual long Id { get; set; }
        public virtual IDictionary<string, ISet<PersistentClass>> MappedCollections { get; set; }
    }

    public class PersistentClass
    {
        public virtual long Id { get; protected set; }
        public virtual string Prop { get; set; }
    }
}

and this is my mapping:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

  <class name="com.example.Foo, com.example">
    <id name="Id" type="Int64" generator="hilo" />
    <map name="MappedCollections">
      <key column="Id" />
      <index column="Key" type="String" />
      <many-to-many class="com.example.PersistentClass, com.example" />
    </map>
  </class>

  <class name="com.example.PersistentClass, com.example">
    <id name="Id" type="Int64" generator="hilo" />
    <property name="Prop" />
  </class>

</hibernate-mapping>

Creating a schema generates the following SQL (SqlServer example):

if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FKC8D94E45A4783B9]') AND parent_object_id = OBJECT_ID('MappedCollections'))
alter table MappedCollections  drop constraint FKC8D94E45A4783B9


if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FKC8D94E46534DBE0]') AND parent_object_id = OBJECT_ID('MappedCollections'))
alter table MappedCollections  drop constraint FKC8D94E46534DBE0


if exists (select * from dbo.sysobjects where id = object_id(N'Foo') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table Foo

if exists (select * from dbo.sysobjects where id = object_id(N'MappedCollections') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table MappedCollections

if exists (select * from dbo.sysobjects where id = object_id(N'PersistentClass') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table PersistentClass

if exists (select * from dbo.sysobjects where id = object_id(N'hibernate_unique_key') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table hibernate_unique_key

create table Foo (
    Id BIGINT not null,
   primary key (Id)
)

create table MappedCollections (
    Id BIGINT not null,
   elt BIGINT not null,
   Key NVARCHAR(255) not null,
   primary key (Id, Key) -- !! WRONG !! should be unique (Id, elt, Key)
)

create table PersistentClass (
    Id BIGINT not null,
   Prop NVARCHAR(255) null,
   primary key (Id)
)

alter table MappedCollections 
    add constraint FKC8D94E45A4783B9 
    foreign key (elt) 
    references PersistentClass

alter table MappedCollections 
    add constraint FKC8D94E46534DBE0 
    foreign key (Id) 
    references Foo

create table hibernate_unique_key (
     next_hi BIGINT 
)

insert into hibernate_unique_key values ( 1 )

Any idea what I am doing wrong? From our SQL, we can see it's persisting as IDictionary<string, PersistentClass> instead of IDictionary<string, ISet<PersistentClass>, I don't want a many-to-many relationship of many Foo to many pairs of string and Persistent class, where the pair is unique for each Foo. All three values should create a unique value.

How can I do this?

(note: I included Hibernate tags because the xml mappings for this relationship should be the same whether Hibernate or NHibernate)


Solution

  • Although it creates an unnecessary join, creating another entity can do this, while keep a very similar public interface.

    basically:

    namespace com.example // Assembly = com.example
    {
    
        public class Foo
        {
            public virtual long Id { get; set; }
    
            public virtual ReadOnlyDictionary<string, ISet<PersistentClass>> MappedCollections 
            { 
                get 
                { 
                    return new ReadOnlyDictionary<string, ISet<PersistentClass>>(_mc); 
                } 
            }
    
            protected virtual IDictionary<string, PersistentClassSet> _mc { get; set; }
            public virtual void InitializeCollection(string key)
            {
                if (!_mk.ContainsKey(key))
                    _mc[key] = new PersistentClassSet();
            }
        }
    
        public class PersistentClass
        {
            public virtual long Id { get; protected set; }
            public virtual string Prop { get; set; }
        }
    
        internal class PersistentClassSet : ISet<PersisitentClass>
        {
            public PersistentClassSet()
            {
                Proxy = new HashSet<PersistentClass>();
            }
    
            protected virtual long Id { get; set; }
            protected virtual ISet<PersistentClass> Proxy { get; set; }
    
            public bool Add(PersistentClass item)
            {
                return Proxy.Add(item);
            }
    
            // other ISet implementations delegated to Proxy 
        }
    }
    

    with mapping as follows:

    <?xml version="1.0" encoding="utf-8"?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
    
      <class name="com.example.Foo, com.example">
        <id name="Id" type="Int64" generator="hilo" />
        <map name="MappedCollections">
          <key column="Id" />
          <index column="Key" type="String" />
          <many-to-many class="com.example.PersistentClassSet, com.example" />
        </map>
      </class>
    
      <class name="com.example.PersistentClass, com.example">
        <id name="Id" type="Int64" generator="hilo" />
        <property name="Prop" />
      </class>
    
      <class name="com.example.PersistentClassSet, com.example">
        <id name="Id" type="Int64" generator="hilo" />
        <set name="Proxy">
          <key column="Id"/>
          <many-to-many class="com.example.PersistentClass, com.example" />
        </set>
      </class>
    </hibernate-mapping>