I've spent some time today enhancing the NHibernate Starter Kit. My previous post describes what the kit is about in greater detail, so please give it a read. To recap, though, the kit is intended to be a means to get started with NHibernate within minutes. Once installed you'll have a new project template within Visual Studio that contains all NHibernate binaries, a simple domain project demonstrating some common mappings, a console application that generates a database and some test data, and some example unit tests that tie it all together.
It is not intended to be a demonstration of advanced features of NHibernate, nor a demonstration of any best practices. Once this kit has got you started there are many better resources out there to teach you those. I've intentionally kept things as simple as possible.
A common objection to NHibernate is that it's too complicated. I want this kit to prove that objection wrong.
So what's changed?
I wasn't too delighted about the PersistentEntity class to begin with. I didn't want anyone to assume that this was a requirement, nor did I want to have to create an enterprise ready class like that found in the S#arp Architecture. Having a base class for entities in NHibernate does help though, so I tried to keep it as simple as possible.
Unfortunately my class had some issues that would result in confusing bugs. The previous override of object.Equals() looked more or less like this:
PersistentEntity other = obj as PersistentEntity;
if (this == other)
{
return true;
}
if (other == null)
{
return false;
}
return this.Id == other.Id;
This was problematic since two transient entities (newly created, and not yet saved to the database) would evaluate as equal. The id of the entity is only assigned once the object has been saved, and thus that last statement would be true for any two unsaved entities! "Thanks for that great starter kit, Ben..."
The next problem was with the naive override of object.GetHashCode():
public override int GetHashCode()
{
return Id;
}
This works for many situations, but falls flat in others. Firstly (and most seriously), this means that two transient entities will have the same hash code, and once saved they will suddenly have a different hash! Secondly persistent entities of different types have the same id, they too will both have the same hash code.
The default mapping used a hilo generator for id's. In a nutshell, hilo uses a sequence of unique ids to assign to entities as they are saved. The sequence is tracked using a separate table in the database, and this results in every entity being given a unique id. I like hilo since it gives the flexibility of guids (don't have to worry about the autoincrement quirks of various databases, for one, nor the constraint of hitting the database to get an id), with the usability of integers (if you ever need to manage the database by hand integers are a lot easier to work with than guids). If anyone changed the mapping away from hilo to one that gave different types the same id, GetHashCode() would start causing problems.
So I've reluctantly changed the id of the base class from an integer to an guid. PersistentEntity now has the following implementations (courtesy of Gabriel Schenker's good article up on nhforge):
public override bool Equals(object obj)
{
PersistentEntity other = obj as PersistentEntity;
if (this == other)
{
return true;
}
if (other == null)
{
return false;
}
//Transient entities will both have the same id, and as such we must check for reference equality.
if (this.Id == Guid.Empty || other.Id == Guid.Empty)
{
return ReferenceEquals(this, other);
}
return this.Id == other.Id;
}
public override int GetHashCode()
{
// Once we have a hash code we'll never change it
if (_oldHashCode.HasValue)
{
return _oldHashCode.Value;
}
// When this instance is transient, we use the base GetHashCode()
// and remember it, so an instance can NEVER change its hash code.
if (Id == Guid.Empty)
{
_oldHashCode = base.GetHashCode();
return _oldHashCode.Value;
}
return Id.GetHashCode();
}
This was the right choice. It's kept PersistentEntity as simple as possible, at the small cost of making some manual DBA work a little harder.
Some more mappings
In the interests of getting started more quickly, I've added some more entities and mappings to the starter kit.
I've thrown together the classes as seen on the left. Again nothing fancy, just a means to illustrate common mappings.
A venue has a one-to-many to courses, which is mapped to cascade="all".
A course has a many-to-one back to the venue, and well as a many-to-many to the students in the course.
A student has a many-to-many to courses.
There are some simple tests that verify some of this behaviour. By no means conclusive, but enough to get started with.
Picking up the theme yet?
I've also realised that the template will not work without some effort in Visual Studio 2005. I might address this in future, but for now have limited the vsi to only install to Visual Studio 2008.
To remove the previous version, simply delete the old NHibernateStarterKit.zip file from "[My Documents]\Visual Studio 2008\Templates\ProjectTemplates\Visual C#", and the corresponding 2005 if applicable.
As before, if you have any suggestions or problems, please contact me any which way you please.
NHibernateStarterKit.vsi (738.18 kb)