VALUE OBJECTS AS IDS USING NHIBERNATE

2016 05 08

Author: Tomaš Maconko

Many of simple decimal, string or other values can be wrapped into value object to make domain model stronger. It can include validation or simple domain rules that must be applied each time the value object is created. It is immutable so it can only return the calculated value without changing its state.

Value objects can be created using range of technologies and depending on technology the implementation may vary. In this post I will be talking about value objects mapping using NHibernate (FluentNHibernate) and the last problem we faced with entity ID as value object.

In last project we had a class named Project, which included logic depending on ProjectStatus (through StatusId property). First we had IDs as integers and we had no problems until the number of checks for specific IDs was growing.

[csharp]
public class Project : EntityBase
{

public virtual int Estimate { get; protected set; }
public virtual int StatusId { get; protected set; }
public virtual void UpdateEstimate(int newEstimate)
{
if (newEstimate < 0)
throw new ArgumentException("New estimate cannot be negative!", nameof(newEstimate));
ProjectStatuses - class for predefined statuses IDs
if (StatusId >= ProjectStatuses.InProgress)
throw new Exception("Project is already in progress!");
Estimate = newEstimate;
}
...

}

public class ProjectStatus : EntityBase
{

public virtual int Id { get; protected set; }
public virtual string Name { get; protected set; }
...

}

[/csharp]

We created ProjectStatusId type to hide part of checks inside and include some validation inside.

[csharp]
public class ProjectStatusId : ValueObjectBase
{
public int StatusId { get; protected set; }

//ProjectStatuses - class for predefined statuses IDs
public bool IsInProgress => StatusId >= ProjectStatuses.InProgress.StatusId;
protected ProjectStatusId() { //NHibernate }
public ProjectStatusId(int statusId)
{
if (statusId <= 0)
throw new ArgumentException("Status id value must be a positive integer", nameof(statusId));
...
StatusId = statusId;
}

}

public class ProjectStatus : EntityBase
{

public virtual ProjectStatusId Id { get; protected set; }
...

}

public class Project : EntityBase
{

public virtual ProjectStatusId StatusId { get; protected set; }
...
public virtual void UpdateEstimate(int newEstimate)
{
...
if (StatusId.IsInProgress)
throw new Exception("Project is already in progress!");
...
}

}
[/csharp]

Before the new type was applied all NHibernate mapping were OK, but after we applied ProjectStatus class the mapping errors appeared. Here we did not find any information about problem solution and tried things by ourselves.

We had already successfully applied the composite-id value objects using CompositeId method in NHibernate mappings. It was containing from two to four different keys inside, but it has been working. So we tried CompositeId for single key property and it worked!

There I provide mappings with ProjectStatusId value object.

[csharp]
public class ProjectMap : ClassMap
{
public ProjectMap()
{
Map(x => x.Description);
Map(x => x.Estimate);

    Component(x => x.StatusId, x => x.Map(c => c.StatusId));
}

}

public class ProjectStatusMap : ClassMap
{
public ProjectStatusMap()
{
CompositeId(x => x.Id).KeyProperty(x => x.StatusId);

    Map(x => x.Name);
}

}
[/csharp]

Here you see that ProjectStatus has Id mapped using CompositeId and in Project we map StatusId using Component method. Sure, both of them are implemented in a way the NHibernate likes (virtual, protected, empty constructor), but still the result is worthy!