Сomponent Сollections Support in Entity Developer

May 24th, 2012

This article explains and gives a practical example of how support of сomponent collections is implemented in Entity Developer for NHibernate. You can take a close look at this type of mapping in the NHibernate documentation here.
This article contains examples of mapping for the following associations:

  • Classic mapping of component collections;
  • A special case of component collections mapping for a many-to-many association.

Classic mapping of component collections:

Classic mapping of component collections is pretty much alike mapping of a one-to-many association between two entities of a model. The major difference is that the model does not have an entity corresponding to a details table, and instead of it the model contains a complex type consisting of a set of properties similar to a part of the details table field set. Whereas an association is built between an entity corresponding to a master table, and a complex type corresponding to a part of the details table field set. The master entity contains a navigation property which is a reference to a collection of complex type details.

Within the database, one-to-many mapping is arranged as a table with a primary key for the one side of mapping, and a table containing a field which references the primary key of the master table, for the other side.

Example:

The database contains the Dept and Emp tables, which have a foreign key constraint between them.
The DEPTNO field in the Emp table receives the ID value of the associated record in the Dept table.

Database Diagram

We perform the following sequence of operations:

  • create a NHibernate model;
  • add the Dept and Emp tables to the model;
  • select a part of Emp entity properties (e.g. the properties ENAME, JOB, MGR), and by dragging and dropping detach them into a separate complex type, call it PartialEmpType, and then delete the Emp entity;
  • after that we add an association between the Dept master entity and the PartialEmpType details complex type and customize it as displayed in the screenshot below:

Association Editor

In the Association Editor dialog box, besides the regular parameters of a one-to-many association like collection type, navigation properties names etc., in the Join Table input field the details table name is specified, and in the Schema field the name of the schema it exists in, is entered. In the dialog box in is necessary to set the name of the foreign key column of the details table, which references the primary key of the master table.
This dialog box also makes it possible to define private mapping for the details complex type properties to the details table fields, if this association mapping is different from general mapping of complex type properties.

By default for this association type for End 1 of the navigation property the Generate related property check-box is off, but if you set it, the parent reference to the master entity will become available for the details complex type.

As a result, we have the following model:

Note: If you define collection type of a navigation property as Set, it is very important to implement Equals() and GetHashCode() correctly for classes corresponding to model complex types. To generate these methods with an Entity Developer template, select the template node in the object tree of the Model Explorer tool window, select the Implement Equals template property in the Properties tool window and set it to True.

The code generated for the model will be as follows:

    

    /// <summary>
    /// There are no comments for Dept in the schema.
    /// </summary>
    public partial class Dept {

        private int _DEPTNO;

        private string _DNAME;

        private string _LOC;

        private Iesi.Collections.Generic.ISet<PartialEmpType> _PartialEmpTypes;

        #region Extensibility Method Definitions

        partial void OnCreated();

        public override bool Equals(object obj)
        {
          Dept toCompare = obj as Dept;
          if (toCompare == null)
          {
            return false;
          }

          if (!Object.Equals(this.DEPTNO, toCompare.DEPTNO))
            return false;

          return true;
        }

        public override int GetHashCode()
        {
          int hashCode = 13;
          hashCode = (hashCode * 7) + DEPTNO.GetHashCode();
          return hashCode;
        }

        #endregion

        public Dept()
        {
            this._PartialEmpTypes = new Iesi.Collections.Generic.HashedSet<PartialEmpType>();
            OnCreated();
        }

        /// <summary>
        /// There are no comments for DEPTNO in the schema.
        /// </summary>
        public virtual int DEPTNO
        {
            get
            {
                return this._DEPTNO;
            }
            set
            {
                this._DEPTNO = value;
            }
        }

        /// <summary>
        /// There are no comments for DNAME in the schema.
        /// </summary>
        public virtual string DNAME
        {
            get
            {
                return this._DNAME;
            }
            set
            {
                this._DNAME = value;
            }
        }

        /// <summary>
        /// There are no comments for LOC in the schema.
        /// </summary>
        public virtual string LOC
        {
            get
            {
                return this._LOC;
            }
            set
            {
                this._LOC = value;
            }
        }

        /// <summary>
        /// There are no comments for PartialEmpTypes in the schema.
        /// </summary>
        public virtual Iesi.Collections.Generic.ISet<PartialEmpType> PartialEmpTypes
        {
            get
            {
                return this._PartialEmpTypes;
            }
            set
            {
                this._PartialEmpTypes = value;
            }
        }
    }

    /// <summary>
    /// There are no comments for PartialEmpType in the schema.
    /// </summary>
    public partial class PartialEmpType {

        private string _ENAME;

        private string _JOB;

        private System.Nullable<int> _MGR;

        #region Extensibility Method Definitions

        partial void OnCreated();

        public override bool Equals(object obj)
        {
          PartialEmpType toCompare = obj as PartialEmpType;
          if (toCompare == null)
          {
            return false;
          }

          if (!Object.Equals(this.ENAME, toCompare.ENAME))
            return false;
          if (!Object.Equals(this.JOB, toCompare.JOB))
            return false;
          if (!Object.Equals(this.MGR, toCompare.MGR))
            return false;

          return true;
        }

        public override int GetHashCode()
        {
          int hashCode = 13;
          return hashCode;
        }

        #endregion

        public PartialEmpType()
        {
            OnCreated();
        }

        /// <summary>
        /// There are no comments for ENAME in the schema.
        /// </summary>
        public virtual string ENAME
        {
            get
            {
                return this._ENAME;
            }
            set
            {
                this._ENAME = value;
            }
        }

        /// <summary>
        /// There are no comments for JOB in the schema.
        /// </summary>
        public virtual string JOB
        {
            get
            {
                return this._JOB;
            }
            set
            {
                this._JOB = value;
            }
        }

        /// <summary>
        /// There are no comments for MGR in the schema.
        /// </summary>
        public virtual System.Nullable<int> MGR
        {
            get
            {
                return this._MGR;
            }
            set
            {
                this._MGR = value;
            }
        }
    }

As can be seen from the code above, the property is generated in the master entity class, containing a reference to the collection of instances of the details complex type class.

Mapping generated for the Dept entity is as follows:

<hibernate-mapping namespace="testmodellerModel" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Dept" table="Dept" schema="dbo">
    <id name="DEPTNO" type="Int32">
      <column name="DEPTNO" not-null="true" precision="10" scale="0" sql-type="int" />
      <generator class="assigned" />
    </id>
    <property name="DNAME" type="String">
      <column name="DNAME" not-null="false" length="14" sql-type="varchar" />
    </property>
    <property name="LOC" type="String">
      <column name="LOC" not-null="false" length="13" sql-type="varchar" />
    </property>
    <set name="PartialEmpTypes" table="Dept_PartialEmpTypes" generic="true">
      <key>
        <column name="DEPTNO" not-null="true" precision="10" scale="0" sql-type="int" />
      </key>
      <composite-element class="PartialEmpType">
        <property name="ENAME">
          <column name="ENAME" not-null="false" length="10" sql-type="varchar" />
        </property>
        <property name="JOB">
          <column name="JOB" not-null="false" length="9" sql-type="varchar" />
        </property>
        <property name="MGR">
          <column name="MGR" not-null="false" precision="10" scale="0" sql-type="int" />
        </property>
      </composite-element>
    </set>
  </class>
</hibernate-mapping>

The NHibernate model for Entity Developer considered in this example can be downloaded here.

Mapping of component collections for a many-to-many association:

Many-to-many associations use an intermediate table that has a foreign key to tables of both associated entities. An object uses several objects of another type, and one of these latter objects refers several objects of the first type. As is always the case for many-to-many mappings in relational databases, we need the third table which provides references for a many-to-many relationship. Mapping like this allows you to map extra columns of a many-to-many association join table to a complex type. Example: The database contains the Employees, Territories and EmployeeTerritories tables, the last one provides references for the many-to-many relationship. The EmployeeTerritories table contains 1 extra column, which is EmployeeTerritoriesDescription. Database Diagram
We perform the following sequence of operations:

  • create a NHibernate model;
  • add the Employees, Territories, and EmployeeTerritories tables;
  • select the EmployeeTerritoriesDescription property of the EmployeeTerritories entity and by dragging and dropping detach it into a separate complex type, call it ‘EmployeeTerritoriesExtra’, delete the EmployeeTerritorу entity;
  • then we add a many-to-many association between the Employee and Territory entities and customize it as displayed in the screenshot:

 

Association Editor
In the Association Editor dialog box, besides the regular parameters of a many-to-many association, such as collection type, navigation properties names, etc., in the Join Table input field the join table name providing references for the many-to-many relationship is specified, and in the Schema field the name of the schema it exists in, is entered. In the dialog box it is necessary to set the name of the foreign key column of the join table, which provides references for the many-to-many relationship. To specify mapping for the join table extra fields for a many-to-many association, in the Component field of the Association Editor dialog box set the required complex type, containing the set of properties corresponding to the set of the join table extra fields. This dialog box also makes it possible to define private mapping for complex type properties to join table extra fields, if this association mapping is different from general mapping of complex type properties. As a result we have the following model:
Data Model
Note: If you define collection type of the navigation property as Set, it is very important to implement Equals() and GetHashCode() correctly for classes corresponding to model complex types. To generate these methods with an Entity Developer template, select the template node in the object tree of the Model Explorer tool window, select Implement Equals template property in the Properties tool window and set it to True. The code generated for the model will be as follows:

   
    /// <summary>
    /// There are no comments for Territory in the schema.
    /// </summary>
    public partial class Territory {

        private string _TerritoryID;

        private string _TerritoryDescription;

        private Iesi.Collections.Generic.ISet<EmployeeTerritoriesExtra> _Employees;

        #region Extensibility Method Definitions

        partial void OnCreated();

        public override bool Equals(object obj)
        {
          Territory toCompare = obj as Territory;
          if (toCompare == null)
          {
            return false;
          }

          if (!Object.Equals(this.TerritoryID, toCompare.TerritoryID))
            return false;

          return true;
        }

        public override int GetHashCode()
        {
          int hashCode = 13;
          hashCode = (hashCode * 7) + TerritoryID.GetHashCode();
          return hashCode;
        }

        #endregion

        public Territory()
        {
            this._Employees = new Iesi.Collections.Generic.HashedSet<EmployeeTerritoriesExtra>();
            OnCreated();
        }

        /// <summary>
        /// There are no comments for TerritoryID in the schema.
        /// </summary>
        public virtual string TerritoryID
        {
            get
            {
                return this._TerritoryID;
            }
            set
            {
                this._TerritoryID = value;
            }
        }

        /// <summary>
        /// There are no comments for TerritoryDescription in the schema.
        /// </summary>
        public virtual string TerritoryDescription
        {
            get
            {
                return this._TerritoryDescription;
            }
            set
            {
                this._TerritoryDescription = value;
            }
        }

        /// <summary>
        /// There are no comments for Employees in the schema.
        /// </summary>
        public virtual Iesi.Collections.Generic.ISet<EmployeeTerritoriesExtra> Employees
        {
            get
            {
                return this._Employees;
            }
            set
            {
                this._Employees = value;
            }
        }
    }

    /// <summary>
    /// There are no comments for Employee in the schema.
    /// </summary>
    public partial class Employee {

        private int _EmployeeID;

        private string _LastName;

        private string _FirstName;

        private System.Nullable<System.DateTime> _BirthDate;

        private System.Nullable<System.DateTime> _HireDate;

        private string _Address;

        private Iesi.Collections.Generic.ISet<EmployeeTerritoriesExtra> _Territories;

        #region Extensibility Method Definitions

        partial void OnCreated();

        public override bool Equals(object obj)
        {
          Employee toCompare = obj as Employee;
          if (toCompare == null)
          {
            return false;
          }

          if (!Object.Equals(this.EmployeeID, toCompare.EmployeeID))
            return false;

          return true;
        }

        public override int GetHashCode()
        {
          int hashCode = 13;
          hashCode = (hashCode * 7) + EmployeeID.GetHashCode();
          return hashCode;
        }

        #endregion

        public Employee()
        {
            this._Territories = new Iesi.Collections.Generic.HashedSet<EmployeeTerritoriesExtra>();
            OnCreated();
        }

        /// <summary>
        /// There are no comments for EmployeeID in the schema.
        /// </summary>
        public virtual int EmployeeID
        {
            get
            {
                return this._EmployeeID;
            }
            set
            {
                this._EmployeeID = value;
            }
        }

        /// <summary>
        /// There are no comments for LastName in the schema.
        /// </summary>
        public virtual string LastName
        {
            get
            {
                return this._LastName;
            }
            set
            {
                this._LastName = value;
            }
        }

        /// <summary>
        /// There are no comments for FirstName in the schema.
        /// </summary>
        public virtual string FirstName
        {
            get
            {
                return this._FirstName;
            }
            set
            {
                this._FirstName = value;
            }
        }

        /// <summary>
        /// There are no comments for BirthDate in the schema.
        /// </summary>
        public virtual System.Nullable<System.DateTime> BirthDate
        {
            get
            {
                return this._BirthDate;
            }
            set
            {
                this._BirthDate = value;
            }
        }

        /// <summary>
        /// There are no comments for HireDate in the schema.
        /// </summary>
        public virtual System.Nullable<System.DateTime> HireDate
        {
            get
            {
                return this._HireDate;
            }
            set
            {
                this._HireDate = value;
            }
        }

        /// <summary>
        /// There are no comments for Address in the schema.
        /// </summary>
        public virtual string Address
        {
            get
            {
                return this._Address;
            }
            set
            {
                this._Address = value;
            }
        }

        /// <summary>
        /// There are no comments for Territories in the schema.
        /// </summary>
        public virtual Iesi.Collections.Generic.ISet<EmployeeTerritoriesExtra> Territories
        {
            get
            {
                return this._Territories;
            }
            set
            {
                this._Territories = value;
            }
        }
    }

    /// <summary>
    /// There are no comments for EmployeeTerritoriesExtra in the schema.
    /// </summary>
    public partial class EmployeeTerritoriesExtra {

        private string _EmployeeTerritoriesDescription;

        #region Extensibility Method Definitions

        partial void OnCreated();

        public override bool Equals(object obj)
        {
          EmployeeTerritoriesExtra toCompare = obj as EmployeeTerritoriesExtra;
          if (toCompare == null)
          {
            return false;
          }

          if (!Object.Equals(this.EmployeeTerritoriesDescription, toCompare.EmployeeTerritoriesDescription))
            return false;

          return true;
        }

        public override int GetHashCode()
        {
          int hashCode = 13;
          return hashCode;
        }

        #endregion

        public EmployeeTerritoriesExtra()
        {
            OnCreated();
        }

        /// <summary>
        /// There are no comments for EmployeeTerritoriesDescription in the schema.
        /// </summary>
        public virtual string EmployeeTerritoriesDescription
        {
            get
            {
                return this._EmployeeTerritoriesDescription;
            }
            set
            {
                this._EmployeeTerritoriesDescription = value;
            }
        }

        #region Ends of the many-to-many association 'Employee_Territory'

        /// <summary>
        /// There are no comments for Employees in the schema.
        /// </summary>
        public Employee Employees
        {
            get;
            set;
        }

        /// <summary>
        /// There are no comments for Territories in the schema.
        /// </summary>
        public Territory Territories
        {
            get;
            set;
        }

        #endregion
    }

As can be seen from the code above, in classes corresponding to entities connected with a many-to-many association, the property is generated containing a reference to the class collection for the complex type, consisting in its turn of a set of properties, corresponding to the set of the join table extra fields. Again, in the complex type class, references are generated to classes corresponding to entities connected with a many-to-many association. Therefore, when a many-to-many association is organized in such a way, we have a possibility to access both values of join table extra columns, by means of referring to complex type fields themselves, and instances of relation classes by referring to the respective complex type reference field.

Mapping generated for the Employee and Territory entities is as follows:

<hibernate-mapping namespace="testmodellerModel" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Territory" table="Territories" schema="ManyToManyExtra">
    <id name="TerritoryID" type="String">
      <column name="TerritoryID" not-null="true" length="20" sql-type="nvarchar" />
      <generator class="assigned" />
    </id>
    <property name="TerritoryDescription" type="String">
      <column name="TerritoryDescription" not-null="true" length="50" sql-type="nchar" />
    </property>
    <set name="Employees" table="EmployeeTerritories" schema="ManyToManyExtra" generic="true">
      <key>
        <column name="TerritoryID" not-null="true" length="20" sql-type="nvarchar" />
      </key>
      <composite-element class="EmployeeTerritoriesExtra">
        <many-to-one name="Employees" class="Employee" fetch="join">
          <column name="EmployeeID" not-null="true" precision="10" scale="0" sql-type="int" />
        </many-to-one>
        <property name="EmployeeTerritoriesDescription">
          <column name="EmployeeTerritoriesDescription" not-null="false" length="50" sql-type="nvarchar" />
        </property>
      </composite-element>
    </set>
  </class>
</hibernate-mapping>

The NHibernate model for Entity Developer considered in this example can be downloaded here.

Comments are closed.