It's often very useful to have a bidirectional relationship between entities. It also simplifies matters for NHibernate, which can often produce more efficient persistence queries when both sides of a relationship are involved.
In this recipe, we will show you how to set up a bidirectional one-to-many relationship between two entity classes.
Bidirectional
to the MappingRecipes
project.Order
class:public class Order { private ISet<OrderItem> _items; private ISet<Project> _projects; public virtual Guid Id { get; protected set; } public Order() { _items = new HashSet<OrderItem>(); _projects = new HashSet<Project>(); } public virtual IEnumerable<OrderItem> Items { get { return _items; } } public virtual IEnumerable<Project> Projects { get { return _projects; } } 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; } public virtual bool ConnectProject(Project project) { if (project != null && _projects.Add(project)) { project.ConnectOrder(this); return true; } return false; } public virtual bool DisconnectProject(Project project) { if (project != null && _projects.Contains(project)) { _projects.Remove(project); project.DisconnectOrder(this); return true; } return false; } }
OrderItem
class:public class OrderItem { protected OrderItem() { } public OrderItem(string name) { Name = name; } public virtual string Name { get; set; } 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); } }
Project
class:public class Project { private ISet<Order> _orders; public Project() { _orders = new HashSet<Order>(); } public virtual Guid Id { get; protected set; } public virtual IEnumerable<Order> Orders { get { return _orders; } } public virtual bool ConnectOrder(Order order) { if (order != null && _orders.Add(order)) { order.ConnectProject(this); return true; } return false; } public virtual bool DisconnectOrder(Order order) { if (order != null && _orders.Contains(order)) { _orders.Remove(order); order.DisconnectProject(this); return true; } return false; } }
Mapping.hbm.xml
:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="MappingRecipes" namespace="MappingRecipes.Bidirectional"> <class name="OrderItem"> <id name="Id"> <generator class="guid.comb" /> </id> <property name="Name"/> <many-to-one name="Order" column="OrderId" /> </class> <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> <set name="Projects" inverse="true" access="field.camelcase-underscore" table="OrderProject"> <key column="OrderId"/> <many-to-many class="Project" column="ProjectId"/> </set> </class> <class name="Project" table="Project"> <id name="Id"> <generator class="guid.comb" /> </id> <set name="Orders" access="field.camelcase-underscore" table="OrderProject"> <key column="ProjectId"/> <many-to-many class="Order" column="OrderId"/> </set> </class> </hibernate-mapping>
Bidirectional
recipe:Note how all entities and relations are inserted at once, without the redundant UPDATE
statements; this is one of the benefits of a properly set up bidirectional relationship.
Object Relational Mapper (ORM) is 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, a single foreign key represents this bidirectional relationship. 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
. Note that we've mapped a set, which implies that the order is not significant, and that duplicates are not allowed.
Notice the use of back ticks 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 back ticks tell NHibernate to surround the identifier with whatever characters may be appropriate for the database you are using.
18.117.231.15