Mapping relations to non-primary keys

In legacy databases, sometimes data is stored in ways that doesn't quite map to an object model. One such scenario is when the relation between for example a Customer and its ContactPersons is controlled by a column other than the primary key in the Customer table. NHibernate provides a way to handle these relations, using the property-ref mapping attribute.

Getting ready

Complete the Getting ready instructions at the beginning of this chapter.

How to do it…

  1. Add a new folder named PropertyRefs to the MappingRecipes project.
  2. Add a class named Customer to the folder:
    using System.Collections.Generic;
    
    namespace MappingRecipes.PropertyRefs
    {
      public class Customer
      {
        public Customer()
        {
          ContactPersons=new HashSet<ContactPerson>();
        }
        public virtual int Id { get; protected set; }
        public virtual string Name { get; set; }
        public virtual ISet<ContactPerson> ContactPersons 
     { 
       get; 
       set; 
     }
        public virtual int CompanyId { get; set; }
    
      }
    }
  3. Add a class named ContactPerson to the folder:
    namespace MappingRecipes.PropertyRefs
    {
      public class ContactPerson
      {
        public virtual int Id { get; protected set; }
        public virtual string Name { get; set; }
        public virtual Customer Customer { get; set; }
      }
    }
  4. Add a new embedded mapping named Mapping.hbm.xml to the folder:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
      assembly="MappingRecipes"
      namespace="MappingRecipes.PropertyRefs">
      <class name="Customer">
        <id name="Id">
          <generator class="native"/>
        </id>
        <property name="Name"/>
        <property name="CompanyId" />
        <set name="ContactPersons" 
           cascade="save-update" 
           inverse="true">
          <key column="CompanyId" 
             property-ref="CompanyId"/>
          <one-to-many class="ContactPerson"/>
        </set>
      </class>
      <class name="ContactPerson">
        <id name="Id">
          <generator class="native"/>
        </id>
        <property name="Name"/>
        <many-to-one name="Customer" 
          class="Customer" column="CompanyId" 
          property-ref="CompanyId" foreign-key="none"/>
      </class>
    </hibernate-mapping>
  5. Add a class named Recipe to the folder:
    using System;
    using NH4CookbookHelpers.Mapping;
    using NHibernate;
    
    namespace MappingRecipes.PropertyRefs
    {
      public class Recipe : HbmMappingRecipe
      {
        protected override void AddInitialData(ISession session)
        {
          var customer = new Customer
          {
            Name = "The customer",
            CompanyId = 345
          };
    
          customer.ContactPersons.Add(
            new ContactPerson
            {
              Customer = customer,
              Name = "Person1"
            }
          );
    
          session.Save(customer);
        }
    
        public override void RunQueries(ISession session)
        {
          var customer = session.Get<ContactPerson>(1);
          Console.WriteLine("Customer:" + customer.Customer.Name);
        }
      }
    }
  6. Run the application and start the PropertyRefs recipe.

How it works…

In our scenario, the relation between the Customer and its ContactPersons is not expressed with a reference to the Customer table's Id column. Instead, we have a different column, with a legacy identifier, called CompanyId. To be able to use this setup in NHibernate, we specify <key column="CompanyId"/> in our set mapping. This is not enough, however. We also need to specify that it's the CompanyId and not the customer's Id that should be stored in that column; this is done by adding property ref="CompanyId".

When the Customer is persisted, INSERT instance like the following will be executed:

INSERT INTO Customer (Name, CompanyId) 
VALUES('The customer', 345)

INSERT INTO ContactPerson (Name, CompanyId) 
VALUES ('Person1', 345)

And when the ContactPersons are retrieved, for our only Customer, the SELECT looks like this:

SELECT [columns]
FROM ContactPerson
WHERE CompanyId=345

As promised, the relationship now is defined by the CompanyId, which in this case is 345.

You may have noticed that we actually added the property-ref twice, once on the set in Customer and once on the many-to-one in ContactPerson. That's not always necessary, but in this case our relation was bidirectional, and NHibernate needs to know about the special relationship on both sides. Had the collection been unidirectional, only the <key column="CompanyId" property-ref="CompanyId"/> would have been needed. If there was no collection, but only a customer property on ContactPerson, the property-ref on the many-to-one would have sufficed.

We also specified in mapping that the CompanyId property should be unique. After all, we want that property to uniquely identify a Customer, even though it is not the primary key. NHibernate does not actively enforce this, but it's required in practice. This is what the documentation says about unique:

unique (optional): enables the DDL generation of a unique constraint for the columns. Also, allow this to be the target of a property-ref.

There's more…

Property-ref is available on most kinds of relationships, such as one-to-one, one-to-many, many-to-one and many-to-many. It should be noted, though, that the purpose is only to support legacy scenarios, where an existing database schema requires it. For all scenarios where the database can be designed to fully support the object model, property-ref's should be avoided. They add unnecessary complexity and without proper database constraints in place (requiring the referenced property to be unique), there's a risk for unexpected behavior or exceptions.

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

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