Minimal and correct way to map one-to-many with NHibernate
Asked Answered
N

2

8

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...
Normannormand answered 1/5, 2015 at 10:20 Comment(6)
It would seem to me that you need a many to many relationship here and not a one to many if addresses are unique in the address table. This would mean a many to many table that stores an employee id and an address id. This way an employee can be linked to one or more addresses and address can belong to more than 1 employee.Cargile
@ColeW I don't want addresses to belong to more than one employee. This is deliberate.Normannormand
If this is true you would need to add an employee id in the address table to identify which addresses belong to which employees. So multiple addresses may exist in the address table with the same employee id. This is how nhibernate would know how to fill private IList<Address> _addresses; in the Employee object. I would also suggest you have an Employee object as part of the Address object as navigation to both sides of the relationship often comes in handy even if it doesn't necessarily make sense from a business logic perspective all the time.Cargile
Why are you adding cascade all from address instead from employee?Frisette
@ColeW a) Could you add this as an answer? b) Is there a way to add the EmployeeID column in the Address DB table without having an Employee reference in the Address entity?Normannormand
@Frisette I thought this is what's required to delete all Address entities when their corresponding Employee entity is deleted. Could you add this as an answer? Thanks.Normannormand
I
11

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.

Insatiable answered 2/5, 2015 at 17:45 Comment(2)
Does NHibernate work exactly the same way with C# as Hibernate does with Java? I ask because the article on mkyong.com about inverse="true" is using Java examples.Normannormand
This piece of code is the same (was ported from Java to C#). The most important is the "spirit of inverse". So, even if there would be any small differences, the concept is the same. Hope it helps ;) Enjoy mighty NHibernate ;)Unison
F
1

You should add the cascade all-delete-orphan in the one-to-many relation, if you delete the Employee the address will be deleted too.

If you don't need Employee reference create a inverse=false relation like this: here

Frisette answered 2/5, 2015 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.