Devart Blog

Support of Many-to-Many Mapping for Component Navigation Properties in Entity Developer

Posted by on May 24th, 2012

This article explains and gives a practical example of how support of сomponent navigation properties is implemented in Entity Developer for NHibernate.
Support of this functionality includes the possibility of detaching to a complex type for further reusability not only scalar entity properties but also navigation properties with subsequent customization of complete mapping at the level of private mapping of a certain property with type equal to complex type of a certain entity.

There are several ways of component navigation properties mapping in NHibernate:

  • Many-to-one;
  • One-to-many;
  • One-to-one;
  • Many-to-many.

In this article we will consider many-to-many mapping of сomponent navigation properties.
To find out more about the rest of the types of сomponent navigation properties mapping, see the article devoted to them here.

Many-to-many mapping of component navigation properties:

This type of mapping can be efficient in case there are several entities in the model connected with many-to-many associations with the same entity, and also possibly (but not obligatorily) having some set of properties similar in meaning and structure.

Then, after creating a model, it makes sense to detach the similar set of the corresponding scalar and navigation properties into a separate complex type, and replace every such set in entities with a single property having type equal to complex type and an appropriate customization of private mapping for this property.

Example:

The database contains the Companies, Factories and CompanyFactories tables, the last one provides references for a many-to-many relation between the Companies and Factories tables.
The database also contains the Shops and CompanyShops tables, the latter also provides references for a many-to-many relation between the Companies and Shops tables.

The script for tables creation for SQL Server DBMS is as follows:

CREATE SCHEMA ManyToMany;
GO

CREATE TABLE ManyToMany.Companies (
  CompanyID int,
  CompanyName nvarchar(15) COLLATE Cyrillic_General_CI_AS NOT NULL,
  CONSTRAINT PK_Companies PRIMARY KEY (CompanyID)
)
GO

CREATE TABLE ManyToMany.Factories (
  FactoryID int,
  FactoryName nvarchar(15) COLLATE Cyrillic_General_CI_AS NOT NULL,
  FactoryAddress nvarchar(60) COLLATE Cyrillic_General_CI_AS NULL,
  FactoryCity nvarchar(15) COLLATE Cyrillic_General_CI_AS NULL,
  FactoryRegion nvarchar(15) COLLATE Cyrillic_General_CI_AS NULL,
  FactoryPostalCode nvarchar(10) COLLATE Cyrillic_General_CI_AS NULL,
  FactoryCountry nvarchar(15) COLLATE Cyrillic_General_CI_AS NULL,
  WorkersCount int,
  CONSTRAINT PK_Factories PRIMARY KEY (FactoryID)
)
GO

CREATE TABLE ManyToMany.Shops (
  ShopID int,
  ShopName nvarchar(15) COLLATE Cyrillic_General_CI_AS NOT NULL,
  ShopAddress nvarchar(60) COLLATE Cyrillic_General_CI_AS NULL,
  ShopCity nvarchar(15) COLLATE Cyrillic_General_CI_AS NULL,
  ShopRegion nvarchar(15) COLLATE Cyrillic_General_CI_AS NULL,
  ShopPostalCode nvarchar(10) COLLATE Cyrillic_General_CI_AS NULL,
  ShopCountry nvarchar(15) COLLATE Cyrillic_General_CI_AS NULL,
  CONSTRAINT PK_Shops PRIMARY KEY (ShopID)
)
GO

CREATE TABLE ManyToMany.CompanyFactories (
  CompanyID int NOT NULL,
  FactoryID int NOT NULL,
  PRIMARY KEY (CompanyID, FactoryID),
  FOREIGN KEY (CompanyID) REFERENCES ManyToMany.Companies (CompanyID),
  FOREIGN KEY (FactoryID) REFERENCES ManyToMany.Factories (FactoryID)
)
GO

CREATE TABLE ManyToMany.CompanyShops (
  CompanyID int NOT NULL,
  ShopID int NOT NULL,
  PRIMARY KEY (CompanyID, ShopID),
  FOREIGN KEY (CompanyID) REFERENCES ManyToMany.Companies (CompanyID),
  FOREIGN KEY (ShopID) REFERENCES ManyToMany.Shops (ShopID)
)
GO

The tables diagram is as follows:

Diagram

We perform the following sequence of operations:

  • create a NHibernate model;
  • add the Companies, Factories, Shops, CompanyFactories and CompanyShops tables to the model.

Before defining a complex type, the model looks as follows:

Initial Model

We select the ShopAddress, ShopCity, ShopRegion, ShopPostalCode, ShopCountry properties and the Companies navigation property of the Shop entity, drag and drop them into a separate complex type and call it ‘ContractorType'; an association appears between this complex type and the Company class and from now on it is the СontractorType complex type that contains the Companies navigation property, and instead of the set of the moved properties the Shop entity contains a property with the type equal to ContractorType, let’s call it Contractor. This is not mandatory, but for the sake of convenience we remove the Shop prefix from the names of the properties and columns of the ContractorType complex type in the default mapping.

The association between ContractorType and the Shop entity looks as follows:

Association Editor window for a many-to-many association

As can be seen from the Association editor dialog box, part of relation settings is in the association itself, for the many-to-many case there is also a default mapping for the foreign key column of the join table for an entity which will contain the property of the specified complex type, but if necessary, it can be changed to a custom one for a certain entity property. This will be shown in our example further in this article.

Since we created the complex type on the basis of the Shop entity properties set, custom mapping of these properties was defined for this entity automatically during dragging and dropping and looks as follows:

Shop entity properties mapping

Now it is necessary to delete the association between the Company and Factory entities, and also delete the FactoryAddress, FactoryCity, FactoryRegion, FactoryPostalCode, FactoryCountry properties of the Factory entity. Instead of these properties set, describing the address and the list of counterpart’s products, it is necessary to add one property with the name ‘Contractor’ and the type equal to ContractorType. Then it is necessary to set custom mapping for the Factory entity’s Contractor property to the Factories table columns as displayed below:

Contractor property custom mapping

As a result we have the following model:

Resulting Model

The code generated for the model is as follows:

    /// <summary>
    /// There are no comments for ContractorType in the schema.
    /// </summary>
    public partial class ContractorType {
        private string _Address;
        private string _City;
        private string _Region;
        private string _Code;
        private string _Country;
        private Iesi.Collections.Generic.ISet<Company> _Companies;    
        #region Extensibility Method Definitions        
        partial void OnCreated();        
        #endregion
        public ContractorType()
        {
            this._Companies = new Iesi.Collections.Generic.HashedSet<Company>();
            OnCreated();
        }    
        /// <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 City in the schema.
        /// </summary>
        public virtual string City
        {
            get
            {
                return this._City;
            }
            set
            {
                this._City = value;
            }
        }    
        /// <summary>
        /// There are no comments for Region in the schema.
        /// </summary>
        public virtual string Region
        {
            get
            {
                return this._Region;
            }
            set
            {
                this._Region = value;
            }
        }    
        /// <summary>
        /// There are no comments for Code in the schema.
        /// </summary>
        public virtual string Code
        {
            get
            {
                return this._Code;
            }
            set
            {
                this._Code = value;
            }
        }    
        /// <summary>
        /// There are no comments for Country in the schema.
        /// </summary>
        public virtual string Country
        {
            get
            {
                return this._Country;
            }
            set
            {
                this._Country = value;
            }
        }    
        /// <summary>
        /// There are no comments for Companies in the schema.
        /// </summary>
        public virtual Iesi.Collections.Generic.ISet<Company> Companies
        {
            get
            {
                return this._Companies;
            }
            set
            {
                this._Companies = value;
            }
        }
    }    
    /// <summary>
    /// There are no comments for Company in the schema.
    /// </summary>
    public partial class Company {
        private int _CompanyID;
        private string _CompanyName;    
        #region Extensibility Method Definitions        
        partial void OnCreated();        
        #endregion
        public Company()
        {
            OnCreated();
        }    
        /// <summary>
        /// There are no comments for CompanyID in the schema.
        /// </summary>
        public virtual int CompanyID
        {
            get
            {
                return this._CompanyID;
            }
            set
            {
                this._CompanyID = value;
            }
        }    
        /// <summary>
        /// There are no comments for CompanyName in the schema.
        /// </summary>
        public virtual string CompanyName
        {
            get
            {
                return this._CompanyName;
            }
            set
            {
                this._CompanyName = value;
            }
        }
    }
    /// <summary>
    /// There are no comments for Shop in the schema.
    /// </summary>
    public partial class Shop {
        private int _ShopID;
        private string _ShopName;
        private ContractorType _Contractor;    
        #region Extensibility Method Definitions        
        partial void OnCreated();        
        #endregion
        public Shop()
        {
            this._Contractor = new ContractorType();
            OnCreated();
        }    
        /// <summary>
        /// There are no comments for ShopID in the schema.
        /// </summary>
        public virtual int ShopID
        {
            get
            {
                return this._ShopID;
            }
            set
            {
                this._ShopID = value;
            }
        }    
        /// <summary>
        /// There are no comments for ShopName in the schema.
        /// </summary>
        public virtual string ShopName
        {
            get
            {
                return this._ShopName;
            }
            set
            {
                this._ShopName = value;
            }
        }    
        /// <summary>
        /// There are no comments for Contractor in the schema.
        /// </summary>
        public virtual ContractorType Contractor
        {
            get
            {
                return this._Contractor;
            }
            set
            {
                this._Contractor = value;
            }
        }
    }
    /// <summary>
    /// There are no comments for Factory in the schema.
    /// </summary>
    public partial class Factory {
        private int _FactoryID;
        private string _FactoryName;
        private System.Nullable<int> _WorkersCount;
        private ContractorType _Contractor;    
        #region Extensibility Method Definitions        
        partial void OnCreated();        
        #endregion
        public Factory()
        {
            this._Contractor = new ContractorType();
            OnCreated();
        }    
        /// <summary>
        /// There are no comments for FactoryID in the schema.
        /// </summary>
        public virtual int FactoryID
        {
            get
            {
                return this._FactoryID;
            }
            set
            {
                this._FactoryID = value;
            }
        }    
        /// <summary>
        /// There are no comments for FactoryName in the schema.
        /// </summary>
        public virtual string FactoryName
        {
            get
            {
                return this._FactoryName;
            }
            set
            {
                this._FactoryName = value;
            }
        }    
        /// <summary>
        /// There are no comments for WorkersCount in the schema.
        /// </summary>
        public virtual System.Nullable<int> WorkersCount
        {
            get
            {
                return this._WorkersCount;
            }
            set
            {
                this._WorkersCount = value;
            }
        }    
        /// <summary>
        /// There are no comments for Contractor in the schema.
        /// </summary>
        public virtual ContractorType Contractor
        {
            get
            {
                return this._Contractor;
            }
            set
            {
                this._Contractor = value;
            }
        }
    }

As can be seen from the generated code, a property is generated in the ContractorType class, containing a reference to the Company class, and the classes Shop and Factory in their turn both contain a Contractor property with type equal to ContractorType, and these properties describe addresses and the list of products of the manufacturer and distributor respectively.

The xml-mapping generated for the model looks as follows:

<hibernate-mapping namespace="testmodellerModel" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Company" table="Companies" schema="ManyToMany">
    <id name="CompanyID" type="Int32">
      <column name="CompanyID" not-null="true" precision="10" scale="0" sql-type="int" />
      <generator class="assigned" />
    </id>
    <property name="CompanyName" type="String">
      <column name="CompanyName" not-null="true" length="15" sql-type="nvarchar" />
    </property>
  </class>
  <class name="Factory" table="Factories" schema="ManyToMany">
    <id name="FactoryID" type="Int32">
      <column name="FactoryID" not-null="true" precision="10" scale="0" sql-type="int" />
      <generator class="assigned" />
    </id>
    <property name="FactoryName" type="String">
      <column name="FactoryName" not-null="true" length="15" sql-type="nvarchar" />
    </property>
    <property name="WorkersCount" type="Int32">
      <column name="WorkersCount" not-null="false" precision="10" scale="0" sql-type="int" />
    </property>
    <component name="Contractor" class="ContractorType">
      <property name="Address" type="String">
        <column name="FactoryAddress" not-null="false" length="60" sql-type="nvarchar" />
      </property>
      <property name="City" type="String">
        <column name="FactoryCity" not-null="false" length="15" sql-type="nvarchar" />
      </property>
      <property name="Region" type="String">
        <column name="FactoryRegion" not-null="false" length="15" sql-type="nvarchar" />
      </property>
      <property name="Code" type="String">
        <column name="FactoryPostalCode" not-null="false" length="10" sql-type="nvarchar" />
      </property>
      <property name="Country" type="String">
        <column name="FactoryCountry" not-null="false" length="15" sql-type="nvarchar" />
      </property>
      <set name="Companies" table="CompanyFactories" schema="ManyToMany" generic="true">
        <key>
          <column name="FactoryID" not-null="true" precision="10" scale="0" sql-type="int" />
        </key>
        <many-to-many class="Company" fetch="join">
          <column name="CompanyID" not-null="true" precision="10" scale="0" sql-type="int" />
        </many-to-many>
      </set>
    </component>
  </class>
  <class name="Shop" table="Shops" schema="ManyToMany">
    <id name="ShopID" type="Int32">
      <column name="ShopID" not-null="true" precision="10" scale="0" sql-type="int" />
      <generator class="assigned" />
    </id>
    <property name="ShopName" type="String">
      <column name="ShopName" not-null="true" length="15" sql-type="nvarchar" />
    </property>
    <component name="Contractor" class="ContractorType">
      <property name="Address" type="String">
        <column name="ShopAddress" not-null="false" length="60" sql-type="nvarchar" />
      </property>
      <property name="City" type="String">
        <column name="ShopCity" not-null="false" length="15" sql-type="nvarchar" />
      </property>
      <property name="Region" type="String">
        <column name="ShopRegion" not-null="false" length="15" sql-type="nvarchar" />
      </property>
      <property name="Code" type="String">
        <column name="ShopPostalCode" not-null="false" length="10" sql-type="nvarchar" />
      </property>
      <property name="Country" type="String">
        <column name="ShopCountry" not-null="false" length="15" sql-type="nvarchar" />
      </property>
      <set name="Companies" table="CompanyShops" schema="ManyToMany" generic="true">
        <key>
          <column name="ShopID" not-null="true" precision="10" scale="0" sql-type="int" />
        </key>
        <many-to-many class="Company" fetch="join">
          <column name="CompanyID" not-null="true" precision="10" scale="0" sql-type="int" />
        </many-to-many>
      </set>
    </component>
  </class>
</hibernate-mapping>

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

Comments are closed.