Bidirectional one-to-many class relationships

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.

How to do it…

  1. Add a new folder named Bidirectional to the MappingRecipes project.
  2. Add the following 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;
      }
    }
  3. Add the following 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);
    
      }
    }
  4. Add the following 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;
      }
    }
  5. Add the mapping for the three classes as an embedded resource named 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>
  6. Run the application and start the Bidirectional recipe:
    How to do it…

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.

How it works…

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.

There's more…

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.

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

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