NHibernate one-to-one mapping - full object graph is not saved

516 views Asked by At

I am trying to get NHibernate to save complete object graph in a one-to-one mapping scenario. I have following classes

public class Employee : EntityBase
{
    public virtual string EmployeeNumber { get; set; }
    public virtual Address ResidentialAddress {get; set; }
}

public class Address : EntityBase
{
    public virtual string AddressLine1 { get; set; }
    public virtual string AddressLine2 { get; set; }
    public virtual string Postcode { get; set; }
    public virtual string City { get; set; }
    public virtual string Country { get; set; }
    public virtual Employee Employee { get; set; }
}

I am trying to use one-to-one mapping here so that one employee has only one residential address. My mappings are below

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Employee">
    <id name="Id" generator="hilo" />
    <property name="EmployeeNumber" length="10" />
    <one-to-one name="ResidentialAddress" class="Address" property-ref="Employee" cascade="all" />    
  </class>
</hibernate-mapping>

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Domain" namespace="Domain">
  <class name="Address">
    <id name="Id" generator="hilo" />
    <property name="AddressLine1"/>
    <property name="AddressLine2" />
    <property name="Postcode" />
    <property name="City" />
    <property name="Country" />

    <many-to-one name="Employee" class="Employee" unique="true" />

  </class>
</hibernate-mapping>

I am using following code to save an instance of employee object

using (var transaction = Session.BeginTransaction())
        {
            id = Session.Save(new Employee
            {
                EmployeeNumber = "123456789",
                ResidentialAddress = new Address
                {
                    AddressLine1 = "Address line 1",
                    AddressLine2 = "Address line 2",
                    Postcode = "postcode",
                    City = "city",
                    Country = "country"
                }
            });
            transaction.Commit();
        }

In the above situation, the foreign key on Address back to Employee is always null. But if I change RResidentialAddress property on Employee class so that Employee property is always populated correctly as below

private Address address;
    public virtual Address ResidentialAddress
    {
        get { return address; }
        set
        {
            address = value;

            if (value != null) value.Employee = this;
        }
    }

This makes it work perfectly. Why do I have to set ResidentialAddress.Employee? Am I missing something in the mappings? Should NHibernate not automatically save the complete object graph (and hence determine proper foreign key values).

The above working code concerns me as it may create a problem when called from NHiberante during loading of entity from database.

1

There are 1 answers

2
Radim Köhler On BEST ANSWER

Your are not missing anything.

We (developers) should always care about setting both ends of any bidirectional relation.

When we use one-to-many (without inverse="true") and without setting the many-to-one, we always end up with redundant and unefficient sequence of SQL statements:

  • INSERT child record, place NULL into Parent_ID
  • UPDATE child record, set the Parent_ID with Parent.ID

This is not suggested: 6.8. Bidirectional Associations:

... The non-inverse side is used to save the in-memory representation to the database. We would get an unneccessary INSERT/UPDATE and probably even a foreign key violation if both would trigger changes! The same is of course also true for bidirectional one-to-many associations...

So, that is in one-to-many, many-to-one.


In case of one-to-one

There is no collection persister in place as discussed above. No man in the middle, which would take care about the other end, and issue "unefficient" INSERT and later UPDATE. NHibernate will use standard entity persisters for Employee and Address.

Each association end of the relation (Employee-Address) has its own persister. These could be triggered in a cascade (usually good idea to have <one-to-one ... cascade="all" />)

But each persister needs enough information to create proper INSERT or UPDATE statement. I.e. even C# Address must know about its Employee. The Address persister, will handle INSERT/UPDATE alone, with access to Address instance only.

SUMMARY: We have to set both ends in code.... And that is good practice even if we are not forced (non inverse child mapping with nullable parent column).
BTW: once loaded from DB, we expect that NHibernate will set both ends... why should not we do the same?