Minimal and correct way to map one-to-many with NHibernate

markvgti picture markvgti · May 1, 2015 · Viewed 24k times · Source

I am new to NHibernate and C#, so please be gentle!

I have the following two NHibernate entities:

Employee
{
    private long _id;
    private String _name;
    private String _empNumber;
    private IList<Address> _addresses;

    //Properties...
}

and

Address
{
    private long _id;
    private String _addrLine1;
    private String _addrLine2;
    private String _city;
    private String _country;
    private String _postalCode;

    //Properties
}

and they have a one-to-many relationship from Employee to Address (each employee can have multiple addresses in their record). Conveniently ignoring the fact that more than one employee may reside at the same address.

I understand this from the perspective of objects in memory (the NHibernate entities). What I am struggling with are the mapping files (and I am taking an easy example here). This is what I have come up with so far:

// Intentionally left out XML and <hibernate-mapping> 
// Mappings for class 'Employee'. -->
<class name="Employee" table="Employees">
    <id name="ID">
        <generator class="native">
    </id>

    <property name="Name" />
    <property name="EmpNumber" />

    <bag name="Addresses">
        <key column="AddressId" />
        <one-to-many class="Address" />
    </bag>
</class>

and

// Intentionally left out XML and <hibernate-mapping> .
// Mappings for class 'Address'
<class name="Address" table="Addresses">
    <id name="ID">
        <generator class="native">
    </id>

    // Intentionally left out name="Employee" 
    // as I don't have corresponding field in Address entity.
    <many-to-one class="Employee" column="EmployeeID" cascade="all" />

    <property name="AddrLine1" />
    <property name="AddrLine2" />
    <property name="City" />
    <property name="Country" />
    <property name="PostalCode" />
</class>
  1. Is this correct?
  2. If not, it seems like what I am missing here is a field in the Address entity that is a reference to the corresponding Employee entity. But if so, then I can't understand why this is required: I don't need to fetch an Address from an Employee, only the other way round...

Answer

Radim K&#246;hler picture Radim Köhler · May 2, 2015

Just few hints, summarizing the most suitable standards I found out when working with NHibernate.

1) If there is a bi-directional reference in persitence (DB column), express it in C# code bi-directional as well.

Other words, if a child has reference to parent, parent should have reference to child.

public class Employee
{
    ...
    public virtual IList<Address> { get; set; }
}
public class Address
{
    ...
    public virtual Employee Employee { get; set; }
}

This represents Business Domain as is. Address belongs to Employee and Employee belongs to Address.

If we for some reasons really want to restrict that, we should rather protected modifier, but still keep the reference in C#

2) Use inverse="true". This could be used only if we mapped both sides (as above), and will lead to more "expected and optimized" INSERT and UPDATE scritps

Read more here:

inverse = “true” example and explanation by mkyong

3) Use batch fetching mapping almost everwhere. That will avoid 1 + N issues during querying. Read more:

few details about batch fetching

4) In case, that one object (in our case Employee) is root (the other does not make so much sense without it) - use cascading. Read more:

nhibernate - Create child by updating parent, or create explicitly?

Rules 2,3,4 in a mapping snippets:

<class name="Employee" ... batch-size="25">
  ...
  <bag name="Addresses"
       lazy="true" 
       inverse="true" 
       batch-size="25" 
       cascade="all-delete-orphan" >
    // wrong! This columns is the same as for many-to-one
    //<key column="AddressId" />
    // it is the one column expressing the relation
    <key column="EmployeeId" />
    <one-to-many class="Address" />
  </bag>

<class name="Address" ... batch-size="25">
  ...
  <many-to-one not-null="true" name="Employee" column="EmployeeID" />

3) if we use inverse="true do not forget to assign both sides of relation (mostly critical during creation)

The reason is:

we instruct NHibernate - the other side (Address) is responsible for persisting relation. But to do that properly, that Address needs to have reference to Employee - to be able to persist its ID into its column in Address table.

So this should be the standard code to create new Address

Employee employee = ... // load or create new
Address address = new Address 
{
    ...
    Employee = employee, // this is important
};
Employee.Addresses.Add(address);
session.SaveOrUpdate(employee);  // cascade will trigger the rest

We can also introduce some method like AddAddress() which will hide this complexity, but setting both sides is a good prectice.