One to one (or zero) relationship using NHibernate

Hallstein Brøtan

One-to-one (or zero) is a common relationship. It's when you want to store some optional information about something, but don't want all the columns in the same table. Still, I often find it unnecessary complicated when using an ORM like NHibernate or Entity Framework. I also see people avoiding this relationship by creating a one-to-many relationship, even if it's not, which is a dirty solution.

This blog post shows one way of setting up this relationship using NHibernate.
Based on NHibernate version 3.3.0.

  • Case scenario
  • Code
  • Fluent mapping solution
  • Common error scenarios

Case scenario

For this made-up case, let's say we have a huge database with sheep. Every time a sheep goes to the slaughterhouse, we need to store some additional data. Since we only need this data for the slaughtered sheep, we store it in a separate table.

This is a one-to-one (or zero) relationship (a sheep can only be slaughtered once), where Sheep is the master table. Notice that the two tables both have SheepId as the primary key. This is how you want to do it for all one-to-one relationships. SlaughteredSheep.SheepId is also the foreign key, and is constrained by Sheep.SheepId.

The ER-diagram looks like this (in a real world scenario, you would have more columns):

The classes:

public class Sheep  
{
    public virtual Guid SheepId { get; protected set; }
    public virtual string NameOfSheep { get; protected set; }
    public virtual SlaughteredSheep SlaughterInfo { get; protected set; }

    public virtual RegisterSlaughterInfo(SlaughteredSheep slaughterInfo )
    {
        SlaughterInfo = slaughterInfo;
    }
}
public class SlaughteredSheep  
{
    public virtual Guid SheepId { get; protected set; }
    public virtual DateTime DateOfSlaughter { get; protected set; }
    public virtual Sheep Sheep { get; protected set; }
}

Code

When a sheep is slaughtered, my code handles a published event, and registers the slaughter info. NHibernate will then automagically persist the data to the database:

public void Handle(ISheepWasSlaughteredEvent slaughteredSheep)  
{
    var sheep = SheepRepository.Get(slaughteredSheep.SheepId);
    slaughteredSheep.sheep = sheep;
    sheep.RegisterSlaughterInfo(slaughteredSheep);
}

Fluent mapping with NHibernate overrides

My solution is elegant and clean. I have choosen Cascade.SaveUpdate instead of Cascade.All because I don't want to delete data automatically.

For Sheep, the Id is generated by the solution, but this can be changed. For SlaughterInfo the Id must be generated based on Sheep.SheepId.

public class SheepOverride : IAutoMappingOverride<Sheep>  
{
    public void Override(AutoMapping<Sheep> mapping)
    {
        mapping.Id(x => x.SheepId).GeneratedBy.Assigned();
        mapping.HasOne(x => x.SlaughterInfo).Cascade.SaveUpdate();
    }
}
public class SlaughterInfoOverride : IAutoMappingOverride<SlaughterInfo>  
{
    public void Override(AutoMapping<SlaughterInfo> mapping)
    {
        mapping.Id(x => x.Sheep).GeneratedBy.Foreign("Sheep");
        mapping.HasOne(x => x.Sheep).Constrained().Cascade.None();
    }
}

Common error scenarios

These are some common error scenarios I've encountered, and what they mean:

Error message:
Batch update returned unexpected row count from update; actual row count: 0; expected: 1

Explanation:
NHibernate is trying to update a row that doesn't exist for the SlaughteredSheep table, instead of inserting like it's supposed to. Since it's trying to update a row that doesn't exist, the updated row count = 0.

Solution:
Don't specify SlaughteredSheep.SheepId, because NHibernate will handle it as an existing entity, and will try to update it. Instead, let the mapping generate the Id from Sheep.SheepId.
Error message:
Attempted to assign id from null one-to-one property: Sheep

Explanation:
SlaughterInfo.Sheep is null.

Solution:
Set the value for Sheep in SlaughteredSheep => slaughteredSheep.sheep = sheep.
Error message:
System.Data.SqlClient.SqlException : Invalid column name 'SlaughteredSheepId'

Explanation:
This would happen if using References instead of HasOne in the SheepOverride fluent mapping. HasOne creates a one-to-one mapping, while References creates a traditional many-to-one relationship. By convention NHibernate tries to map the -TableName-Id as the primary key.

Solution:
Use HasOne instead of References in the mapping.
Error message:
Invalid index n for this SqlParameterCollection with Count=n.

Explanation:
Several scenarios can cause this error message, but the essence is that a property is mapped several times. In this scenario it would probably be caused by using References in the SheepOverride fluent mapping, even if specifying the correct column to map => mapping.References(x => x.SlaughteredSheep, "SheepId"). Now the foreign key will be exposed as well as using a many-to-one relationship.

Solution:
Use HasOne instead of References in the mapping.
NHibernate">
comments powered by Disqus