In some cases, it's useful to have a bidirectional relationship between entities. In this recipe, I'll show you how to set up a bidirectional one-to-many relationship between two entity classes.
ManualRelationships
.Iesi.Collections.dll
in the Lib
folder.Order
class:public class Order { public virtual Guid Id { get; protected set; } public Order() { _items = new HashedSet<OrderItem>(); } private ISet<OrderItem> _items; public virtual IEnumerable<OrderItem> Items { get { return _items; } } public virtual bool AddItem(OrderItem newItem) { if (newItem != null && _items.Add(newItem)) { newItem.SetOrder(this); return true; } return false; } public virtual bool RemoveItem( OrderItem itemToRemove) { if (itemToRemove != null && _items.Remove(itemToRemove)) { itemToRemove.SetOrder(null); return true; } return false; } }
Order.hbm.xml
:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="ManualRelationships" namespace="ManualRelationships"> <class name="Order" table="`Order`"> <id name="Id"> <generator class="guid.comb" /> </id> <set name="Items" cascade="all-delete-orphan" inverse="true" access="field.camelcase-underscore"> <key column="OrderId" /> <one-to-many class="OrderItem"/> </set> </class> </hibernate-mapping>
OrderItem
class:public class OrderItem { public virtual Guid Id { get; protected set; } public virtual Order Order { get; protected set; } public virtual void SetOrder(Order newOrder) { var prevOrder = Order; if (newOrder == prevOrder) return; Order = newOrder; if (prevOrder != null) prevOrder.RemoveItem(this); if (newOrder != null) newOrder.AddItem(this); } }
OrderItem.hbm.xml
:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="ManualRelationships" namespace="ManualRelationships"> <class name="OrderItem"> <id name="Id"> <generator class="guid.comb" /> </id> <many-to-one name="Order" column="OrderId" /> </class> </hibernate-mapping>
Object relational mappers (ORM) are designed to overcome the impedance mismatch between the object model in the application and the relational model in the database. This mismatch is especially evident when representing a bidirectional one-to-many relationship between entities. In the relational model, this bidirectional relationship is represented by a single foreign key. In the object model, the parent entity has a collection of children, and each child has a reference to its parent.
To work around this mismatch, NHibernate ignores one side of the bidirectional relationship. The foreign key in the database is populated based on either the OrderItems
reference to the Order
or the Orders
collection of OrderItems
, but not both. We determine which end of the relationship controls the foreign key using the inverse
attribute on the collection. By default, the Order
controls the foreign key. Saving a new Order
with one OrderItem
will result in the following three SQL statements:
INSERT INTO "Order" (Id) VALUES (@p0) INSERT INTO OrderItem (Id) VALUES (@p0) UPDATE OrderItem SET OrderId = @p0 WHERE Id = @p1
When we specify inverse="true"
, the OrderItem
controls the foreign key. This is preferable because it eliminates the extra UPDATE
statement, resulting in the following two SQL statements:
INSERT INTO "Order" (Id) VALUES (@p0) INSERT INTO OrderItem (OrderId, Id) VALUES (@p0, @p1)
We are responsible for keeping both sides of our two-way relationship in sync. In a normal class, we would add code in the property setter or the collection's add
or remove
methods to update the other end of the relationship automatically. NHibernate, however, throws exceptions when an object is manipulated while NHibernate is initializing it.
For this reason, it's suggested that we prevent direct manipulation of either end of the relationship, and instead use methods specifically written for this purpose, as we've done here with AddItem
, RemoveItem
, and SetOrder
. Notice that we've mapped a set, which implies that order is not significant, and that duplicates are not allowed.
Notice the use of backticks in our table name from the Order
mapping as follows:
<class name="Order" table="`Order`">
In Microsoft SQL Server, Order
is a keyword. If we want to use it as an identifier, a table name in this case, NHibernate will need to put quotes around it. The backticks tell NHibernate to surround the identifier with whatever character may be appropriate for the database you're using.