NHibernate Starter Kit Enhanced

by Ben Hart 15. November 2008 11:32

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.

Domain

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)

 

Technorati Tags: ,,,

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

C# | NHibernate

NHibernate Starter Kit

by Ben Hart 21. October 2008 17:28

It took me a while to get into NHibernate. I knew it was an awesome tool, with an active community, but it always seemed simpler to just roll-my-own before taking the time to learn a proper ORM. I think part of the problem was accessibility. Coming from a Microsoft development world one gets used to running installers and adding templates to try new products out.

Despite the wealth of tutorials, screen casts, a pretty thorough reference, easily available source code, and a vibrant community, many people still have the impression that NHibernate is hard. It seems that those extra 4 steps to get started (download the latest binary package, extract and add references to NHibernate, add "nhibernate.cfg.xml", and add entity mappings) is enough to deter people, myself included for quite some time.

But why go through this?

Once I broke through the initial resistance, I'm not turning back. It's a great ORM, the first that's entered my world that gets me close to modelling my domain how I'd like it, without much compromise. The ability to throw together some C# classes, test these until I'm happy with their shape and behaviour, and then have a database generated which supports their persistence has liberated me. Most ORMs in the .NET space place too much emphasis on the database, and the temptation to use the tools and wizards has me back in SQL Server Management Studio far more often than I'd like. While I'm not a zealot (at least I try to mention the alternatives when I encourage NHibernate), I do feel more people need this liberation from the database. As a great man once said,

I have a dream that one day the majority of .NET developers will emancipate themselves from the binds of an inherently cumbersome environment.

I have a dream that one day these developers will join me in dropping the notion that "business logic classes" are simply filters between the user interface and the database.

I have a dream that one day even the most hardened of stored procedure advocates will concede that the ability to refactor with ease and unit test in isolation is compelling enough a reason to forever rid themselves of TSQL and its ilk.

Today I took a small step towards that dream.

Nah, I'm just lazy.

I often want to create a quick NHibernate based application, generally for a proof of concept, and I'm tired of finding those dll's on my bloated drive. I'm also tired of copying and pasting the config file, and the plumbing I need to generate a schema.

So I thought I'd save myself some time and create a Visual Studio template that does this for me. In fairness it was a little more of an adventure than I was bargaining for, but in about 20 proofs of concept I should break even. It was interesting to get to know Visual Studio templates, and some of the limitations thereof, so never any time wasted.

When installed (I created a .vsi which is just a zip in disguise, so feel free to unzip it and butcher) a new project template becomes available under Visual C#, "NHibernate Starter Kit".

VSProjects

NHSKSolutionAdding this project creates a solution with three projects, Domain, SchemaGenerator, and Tests. Domain contains a simple base class for entities (by no means required for NHibernate), and an example entity and corresponding mapping.

SchemaGenerator is a console application that uses these mappings to generate a database schema and insert some test data. Rather than create the database from scratch, it assumes that the database as defined in the App.config has already been created.

Tests has an example integration test: creating a new entity, saving to the database, and retrieving it.

I've scattered a few comments around the code for luck.

One of the limitations of a multi-project template is the ability to include files that aren't in a project. Typically we'd all have a lib or dependencies folder that contains common 3rd party libraries, and that was my initial intention. Rather than go the whole hog to get this (which involved implementing IWizard in a class library, signing, GAC'ing, jumping around in frustration), I've cheated by placing them in the bin folder, included in the project. Not ideal, but works fine. (Incidentally, the template includes the latest released version of NHibernate, 4.0.1, and NUnit 2.4.8. I'm actually not sure whether redistributing these is a problem, so drop me a line if I need to take down).

In theory, you should simply be able to install the template, create a database (simply an empty database), match this to the connection string, run the schema generator and marvel how that table is created for you. You might even want to add a few properties to MyEntity, jack out the corresponding mapping, run that schema generator, and marvel some more how the table changes without that pesky designer. Run the NUnit test for luck too.

But that's theory, at this stage all I know is that it works on my machine (Visual Studio 2008, SQL Server 2005 Express). Let me know if it works on yours.

I intend to enhance the kit over time, adding more to the sample entity as a reference for mappings, demonstrating common patterns, and so on.

In the meantime, download the kit, and free yourself from the database.

Update: I've since updated the kit to include a few more mappings, and sorted out some issues with the persistent entity base class. Please see the updated version here.

Technorati Tags:

NHibernateStarterKit.vsi (737.61 kb)

 

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

NHibernate

NHibernate objects dirty on load with nothing set in between

by Ben Hart 9. October 2008 14:35

Or "a tale of a day spent debugging".

We've advanced quite far along in quite a big application using NHibernate. I keep an eye on the statements NHibernate generates, making sure that nothing too untoward is happening. I was inspecting one test (not so great to begin with, one that got everything from a database, just to retrieve the first one), and I noticed that every single item being loaded was being updated on flush.

Hmmmm. Doesn't seem right, my honed QA skills told me. Let's take a look. Doesn't look like anything's set. Let's put some breakpoints in the constructors, and take it from there. Hmmm, this is a mighty big object, loads of children, must be one of them setting something on construction. Strange, nothing there. And so my day continued.

To illustrate, let me introduce you to my friend, Enummy. He's quite simple, only really has an id and an emotion.

public class Enummy
{
    public virtual int Id { get; set; }
    public virtual Emotions Emotion { get; set; }
}
 
public enum Emotions
{
    Happy,
    Sad,
    Frustated,
    Annoyed
}

Naturally we should test that he has the range of emotions, so we had whapped out the following tests.

[Test]
public void Enummy_should_be_happy_before_and_after_save()
{
    int id;
    ISession session = _factory.OpenSession();
    var enummy = new Enummy {Emotion = Emotions.Happy};
    session.Save(enummy);
    session.Flush();
    id = enummy.Id;
 
    session = _factory.OpenSession();
    var loaded = session.Get<Enummy>(id);
    Assert.That(loaded.Emotion, Is.EqualTo(Emotions.Happy));
}
 
[Test]
public void Enummy_should_sometimes_be_sad()
{
    int id;
    ISession session = _factory.OpenSession();
    var enummy = new Enummy {Emotion = Emotions.Sad};
    session.Save(enummy);
    session.Flush();
    id = enummy.Id;
 
    session = _factory.OpenSession();
    var loaded = session.Get<Enummy>(id);
    Assert.That(loaded.Emotion, Is.EqualTo(Emotions.Sad));
}

So far so good. That's the complete round trip, right there, all the way to the database. A proper integration test against SQL Server. We mapped Enummy with the following mapping:

<hibernate-mapping default-cascade="none" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Domain.Enummy, Domain" table="Enummy">
    <id name="Id" type="System.Int32" column="Id" unsaved-value="0">
      <generator class="hilo" />
    </id>
    <property name="Emotion" type="System.Int32" />
  </class>
</hibernate-mapping>

Test pass, so all seems good. Except, of course, for the dirty session (one test you're not likely to write).

[Test]
public void Enummy_should_not_dirty_the_session_when_he_loads()
{
    int id;
    ISession session = _factory.OpenSession();
    var enummy = new Enummy { Emotion = Emotions.Frustated };
    session.Save(enummy);
    session.Flush();
    id = enummy.Id;
 
    session = _factory.OpenSession();
    var loaded = session.Get<Enummy>(id);
    Assert.IsFalse(session.IsDirty());
}

Enummy, little guy, I realise you're frustrated, but must you dirty yourself and the session so? You even do it when you're happy! Assert.That(debugger.Emotion, Is.EqualTo(Emotions.Annoyed)) :(

The problem is with that mapping. An easy mistake to make, especially if you don't realise that NHibernate has no issues with .NET enums, and presumed that an integer (or even smaller) would be appropriate. Enums should be mapped like so, with the full type of the enum specified.

<hibernate-mapping default-cascade="none" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Domain.Enummy, Domain" table="Enummy">
    <id name="Id" type="System.Int32" column="Id" unsaved-value="0">
      <generator class="hilo" />
    </id>
    <property name="Emotion" type="Domain.Emotions, Domain" />
  </class>
</hibernate-mapping>

The cast from Int32 to an enum (albeit implicit at times) is enough to dirty the object. To reiterate (in the interests of good SEO), an NHibernate entity and session will be marked dirty even if nothing is explicitly set if there is a cast from one type to another in the mapping. The values might be equal when one is cast to the type of the other. Until one is cast, though, they are unequal, and as such the object is considered to be dirty. While really hard to debug, I suppose this is the correct behaviour. And once you know about it, debugger.Emotion = Emotions.Happy.

Another item on the code review checklist, watch for enums and other casts in NHibernate mappings, and add an integration test that the object is not dirty straight after a load (for that next newbie to NH who doesn't realise this).

Update: After having struggled through this all on my own, I was relieved to find that I wasn't the only one. I wish I had stumbled across metro-dev extraordinaire Justice Gray's post explaining the issue before. By his reckoning I'm in the 0.0000000000000001%. I like to think I'm even more special.

Technorati Tags:

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

C# | NHibernate | TDD

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About me...

I'm a passionate .NET developer, with C# my language of choice. I've been at it for a number of years now, and enjoy that I'll never shake the feeling I'm just starting out.

I love software, and I love building it even more. I love knowing that my work facilitates others', and that one line of code at a time, we're increasing our capability.

More...



Page List