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.
PropertyRefs
to the MappingRecipes
project.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; } } }
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; } } }
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>
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); } } }
PropertyRefs
recipe.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.
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.
3.143.237.136