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; }
}
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>
<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; }
}
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>
<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