NHibernate One-To-Many mapping, Composite Identifier, Bidirectional relationship

Next topic is the One-To-Many mapping in NHibernate. I will declare the relations as the same way as done it at my previous article: Code First and XML Mapping. Each approaches are right, you can choose the solution which is the best for your project.

For the presentation I have defined two entities:

  • Child
  • Mother

Each Mother have zero or more Children, but a Child belongs to only one Mother. Each side have reference points to the other side, so this is an bidirectional relationship between the entities.

Relations of the entities

The Child entity class code:

    [Serializable]
    [Class(Table = "Children")]
    public class Child : EntityBase
    {

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        [Property(NotNull = true)]
        private string name = string.Empty;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        [ManyToOne(0, Name = "mother", Cascade = "none")]
        [Column(1, Name = "mother_restId")]
        [Column(2, Name = "mother_deviceId")]
        [Column(3, Name = "mother_id")]
        private Mother mother = null;

        public Child()
            : base()
        {
        }

        [DebuggerHidden]
        public virtual string Name
        {
            get { return name; }
            set
            {
                OnPropertyChanging("Name");
                name = value;
                OnPropertyChanged("Name");
            }
        }

        [DebuggerHidden]
        public virtual Mother Mother
        {
            get { return mother; }
            set
            {
                OnPropertyChanging("Mother");
                mother = value;
                OnPropertyChanged("Mother");
            }
        }

    }

The Mother entity class:

    [Serializable]
    [Class(Table = "Mothers")]
    public class Mother : EntityBase
    {

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        [Property(NotNull = true)]
        private string name = string.Empty;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        [Set(0, Name = "children", Generic = true, Lazy = CollectionLazy.True, Cascade = "none")]
        [Key(1)]
        [Column(2, Name = "child_restId")]
        [Column(3, Name = "child_deviceId")]
        [Column(4, Name = "child_id")]
        [OneToMany(5, NotFound = NotFoundMode.Exception, ClassType = typeof(Child))]
        private ISet children = new HashSet();

        public Mother()
            : base()
        {
        }

        [DebuggerHidden]
        public virtual string Name
        {
            get { return name; }
            set
            {
                OnPropertyChanging("Name");
                name = value;
                OnPropertyChanged("Name");
            }
        }

        /// NOTE: the Children property content is immutable.
        /// You should have to get the collection, modify it than re-set across the this property.
        /// The main reason for this solution, you cannot change the content of the set, without property
        /// notification. This is useful, if you bind the property to somewhere.
        [DebuggerHidden]
        public virtual ISet Children
        {
            get { return new HashSet(children); }
            set
            {
                if (value == null)
                {
                    ThrowHelper.ThrowArgumentNullException("value");
                }

                OnPropertyChanging("Children");
                children = new HashSet(value);
                OnPropertyChanged("Children");
            }
        }

    }

As you can see in the Children property comment there is a problem with NHibernate version 3.3.2.4000 GA. If you declare an entity which inherited from Child entity, add it to the Children set than save the mother entity and you will get an exception. This is a known issue in NHibernate, to track this problem please visit this Jira.

https://nhibernate.jira.com/browse/NH-3380

Here is the mapping XML:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping default-access="field" auto-import="true" assembly="NHibernateDemo.DomainModel.OneToMany, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" xmlns="urn:nhibernate-mapping-2.2">
  
  <class table="Children" name="NHibernateDemo.DomainModel.OneToMany.Child, NHibernateDemo.DomainModel.OneToMany">
    <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id">
      <key-property name="systemId" column="systemId" />
      <key-property name="deviceId" column="deviceId" />
      <key-property name="id" column="id" />
    </composite-id>
    
    <property name="name" not-null="true" />
    <property name="entityCreationTime" not-null="true" />
    <property name="entityModificationTime" not-null="true" />
    <property name="deleted" column="isDeleted" not-null="true" />
    
    <many-to-one name="mother" cascade="none">
      <column name="mother_restId" />
      <column name="mother_deviceId" />
      <column name="mother_id" />
    </many-to-one>
    
    <component name="version">
      <property name="versionDeviceId" column="versionDeviceId" not-null="true" />
      <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" />
    </component>
  </class>
  
  <class table="Mothers" name="NHibernateDemo.DomainModel.OneToMany.Mother, NHibernateDemo.DomainModel.OneToMany">
    <composite-id class="NHibernateDemo.Shared.EntityId, NHibernateDemo.Shared" name="id">
      <key-property name="systemId" column="systemId" />
      <key-property name="deviceId" column="deviceId" />
      <key-property name="id" column="id" />
    </composite-id>
    
    <property name="name" not-null="true" />
    <property name="entityCreationTime" not-null="true" />
    <property name="entityModificationTime" not-null="true" />
    <property name="deleted" column="isDeleted" not-null="true" />
    
    <component name="version">
      <property name="versionDeviceId" column="versionDeviceId" not-null="true" />
      <property name="versionSeqNumber" column="versionSeqNumber" not-null="true" />
    </component>
    
    <set name="children" lazy="true" cascade="none" generic="true">
      <key>
        <column name="child_restId" />
        <column name="child_deviceId" />
        <column name="child_id" />
      </key>
      <one-to-many class="NHibernateDemo.DomainModel.OneToMany.Child, NHibernateDemo.DomainModel.OneToMany" not-found="exception" />
    </set>
  </class>
  
</hibernate-mapping>

All entities has a base EntityBase class which contains shared properties for every entities. Every entities has a composite identifier as complex primary key.

Database.One-To-Many

Here is an example, how to create and save entities:

        public void TestOneToMany()
        {
            // create the entities
            using (ISession session = sessionFactory.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    Mother victoria = new Mother();
                    EFUtils.InitializeEntityForThisDemo(victoria);
                    victoria.Name = "Victoria";
                    EFUtils.SaveEntity(victoria, session);

                    Child tom = new Child();
                    EFUtils.InitializeEntityForThisDemo(tom);
                    tom.Name = "Tom";
                    EFUtils.SaveEntity(tom, session);

                    Child sue = new Child();
                    EFUtils.InitializeEntityForThisDemo(sue);
                    sue.Name = "Sue";
                    EFUtils.SaveEntity(sue, session);

                    // each mother may have several children, but each children belongs to one mother
                    ISet children = new HashSet();
                    children.Add(tom);
                    children.Add(sue);
                    victoria.Children = children;
                    EFUtils.SaveEntity(victoria, session);

                    tom.Mother = victoria;
                    sue.Mother = victoria;
                    EFUtils.SaveEntity(tom, session);
                    EFUtils.SaveEntity(sue, session);

                    transaction.Commit();
                }
            }

            using (ISession session = sessionFactory.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    DetachedCriteria dc = DetachedCriteria.For();
                    ICriteria criteria = dc.GetExecutableCriteria(session);
                    IList mothers = criteria.List();
                    Assert.IsTrue(mothers != null);
                    Assert.IsTrue(mothers.Count == 1); // there is only one mother exists in database
                    Assert.IsTrue(mothers[0].Children.Count == 2); // two children assigned

                    dc = DetachedCriteria.For();
                    dc.Add(Restrictions.Not(Restrictions.IsNull("mother")));
                    criteria = dc.GetExecutableCriteria(session);
                    IList children = criteria.List();
                    Assert.IsTrue(children != null);
                    Assert.IsTrue(children.Count == 2);
                    Assert.IsTrue(children[0].Mother != null);
                    Assert.IsTrue(children[0].Mother.Equals(mothers[0]));
                    Assert.IsTrue(children[1].Mother != null);
                    Assert.IsTrue(children[1].Mother.Equals(mothers[0]));

                    transaction.Rollback();
                }
            }

        }

Download the One-To-Many Visual Studio 2010 example project:

JZO névjegye

I'm a full-stack developer, coding in .NET/React/Javascript. Visit my site: https://jzo.hu
Kategória: NHibernate
Címke: , , , , , ,
Közvetlen link a könyvjelzőhöz.

Hozzászólás