Click here to Skip to main content
Click here to Skip to main content

NHibernate Mappings for Composite Keys with Associations

, 5 Aug 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
This example demonstrates how to work on mappings with associations for legacy database tables designed purely using multiple primary keys/composite keys.

Introduction

When I was integrating NHibernate into an existing enterprise software with a legacy database, it took me some time to figure out how mappings for composite keys with associations between entities should be set. This simplified example might be useful to start with for who's trying the same thing.

Background

The article NHibernate and Composite Keys by Anne Epstein provides a thorough explanation on fundamental composite keys mapping and supporting lazy loading by declaring the composite keys as a class object. Here we only focus on a simple code sample of the associations between entities with composite keys.

Using the Code

Considering 2 sample tables, Products and Orders:

Products

Name Type
StoreID NUMBER PK
ProductID NUMBER PK
ProductName VARCHAR

Orders

Name Type
StoreID NUMBER PK
ProductID NUMBER PK
OrderID NUMBER PK
Amount NUMBER

There is no relationship between both tables in the database but apparently the association from Products to Orders is one-to-many.

We define a ProductIdentifier class as the ID of the Product entity class which contains the primary keys, StoreID and ProductID.

In the Product entity class definition, we add a ISet collection property of Order entity type. The collection property represents the Order class objects associated with the Product class object. We need to include the Iesi.Collection library which comes with the NHibernate package.

using Iesi.Collections;

[Serializable]
public class ProductIdentifier
{
    public virtual int StoreID { get; set; }
    public virtual int ProductID { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        ProductIdentifier id;
        id = (ProductIdentifier)obj;
        if (id == null)
            return false;
        if (StoreID == id.StoreID && ProductID == id.ProductID)
            return true;
        return false;
    }

    public override int GetHashCode()
    {
        return (StoreID + "|" + ProductID).GetHashCode();
    }
}

public class Product
{
    public virtual ProductIdentifier ProductIdentifier { get; set; }

    public virtual string ProductName { get; set; }

    public virtual ISet Orders { get; set; }

    //public virtual int Version { get; set; }
} 

In the Product XML mapping file the ID of Product entity is mapped by <composite-id> element.

The ISet collection property defined previously is mapped by <set> element:

  • The <key> elements here is like the foreign key of the Orders table, in this case they are StoreID and ProductID.
  • The <one-to-many> element specifies the association.
  • Setting the inverse attribute to true specifies that the collection is a mirror image of the many-to-one association in the Order entity.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="Product" table="Products" lazy="true" >
    <composite-id name="ProductIdentifier" class="ProductIdentifier">
      <key-property name="StoreID" column="StoreID" />
      <key-property name="ProductID" column="ProductID" />
    </composite-id>
    <!--<version name="Version" column="Version" />-->
    <set name="Orders" inverse="true">
      <key>
        <column name="StoreID"/>
        <column name="ProductID"/>
      </key>
      <one-to-many class="Order"/>
    </set>
    <property name="ProductName" column="ProductName" type="String" />
  </class>
</hibernate-mapping><span style="white-space: normal; ">
</span>

The mapping of Orders table is similar to what we just did above. Since it has a many-to-one association with Products, we add a Product class type property in the Order class definition.

[Serializable]
public class OrderIdentifier
{
    public virtual int StoreID { get; set; }
    public virtual int ProductID { get; set; }
    public virtual int OrderID { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;
        OrderIdentifier id;
        id = (OrderIdentifier)obj;
        if (id == null)
            return false;
        if (StoreID == id.StoreID && 
        ProductID == id.ProductID && OrderID == id.OrderID)
            return true;
        return false;
    }

    public override int GetHashCode()
    {
        return (StoreID + "|" + 
        ProductID + "|" + OrderID).GetHashCode();
    }
}

public class Order
{
    public virtual OrderIdentifier OrderIdentifier { get; set; }

    public virtual int Amount { get; set; }

    public virtual Product Product { get; set; }

    //public virtual int Version { get; set; }
}

In the corresponding mapping file, the <many-to-one> element specifies the association with Product entity and the <column> elements are the foreign keys of the Orders table referring to the Products table.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="Order" table="Orders" lazy="true" >
    <composite-id name="OrderIdentifier" class="OrderIdentifier">
      <key-property name="StoreID" column="StoreID" />
      <key-property name="ProductID" column="ProductID" />
      <key-property name="OrderID" column="OrderID" />
    </composite-id>
    <!--<version name="Version" column="Version" />-->
    <many-to-one name="Product" class="Product" 
    insert="false" update="false">
      <column name="StoreID" />
      <column name="ProductID" />
    </many-to-one>
    <property name="Amount" column="Amount" />
  </class>
</hibernate-mapping>

That’s all needed for the association to work. Adding a <version> element in the mapping is recommended for long transactions if adding a new version column of the table in the legacy database is allowed.

History

  • 2012-8-6 Corrected <many-to-one> element description
  • 2012-7-12 Submitted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

begeeben
Software Developer
Taiwan Taiwan
Developing enterprise software and web application in an aviation company in Taiwan. I'm interested in patterns, architectures and new tech that make life easier.

Comments and Discussions

 
QuestionFluent Mapping PinmemberAndreanta20-Oct-12 2:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411022.1 | Last Updated 6 Aug 2012
Article Copyright 2012 by begeeben
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid