Minimal and correct way to map one-to-many with NHibernate
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>
- Is this correct?
- If not, it seems like what I am missing here is a field in the
Address
entity that is a reference to the correspondingEmployee
entity. But if so, then I can't understand why this is required: I don't need to fetch anAddress
from anEmployee
, only the other way round...
Solution 1:
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 inC#
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, thatAddress
needs to have reference toEmployee
- 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.
Solution 2:
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