Bidirectional one-to-many class relationships

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.

How to do it...

  1. Create an empty class library project named ManualRelationships.
  2. Add a reference to Iesi.Collections.dll in the Lib folder.
  3. Add the following 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;
      }
    
    }
  4. Add the following mapping as an embedded resource named 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>
  5. Add the following 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);
    
      }
    
    }
  6. Add the following mapping as an embedded resource named 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>

How it works...

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.

There's more...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.165.62