NHibernate lets you map C# enums to database columns. This can be quite convenient in cases where you have a database column for something like gender. To map them, you follow the procedure outlined here.

That works all fine and dandy, as long as the strings you want to store in the database are valid C# identifiers.

What do I mean? Well, consider an enum of something like shipping countries. Imagine we're only shipping to Canada and the USA. That's easy enough:

// Country.cs
public enum Country
{
  Canada,
  USA
}

Now imagine that after we write that code, we find out the customer hates TLAs and wants the database entry to contain "United States of America". When we go to the enum to change it, we realize we're screwed. You can't have spaces in a C# identifier!

// Country.cs
public enum Country
{
  Canada,
  United States of America // Compile error!
}

Fortunately, the ever-flexible NHibernate can handle this case using the procedure outlined here.

Let's apply this procedure to the shipping locations example. Recall that C# was not happy with us putting spaces in our enum members. How can we make that code compile? Well, an underscore is a valid part of a C# identifier, and it's kinda like a space, so let's try that:

// Country.cs
public enum Country
{
  Canada,
  United_States_of_America // C# is happy!
}

Next, we need to derive our EnumStringType class so that NHibernate will store the string value of the enum instead of the integer value:

// CountryEnumStringType.cs
class CountryEnumStringType : NHibernate.Type.EnumStringType
{
  // The 30 is the size of the field in the DB.
  public CountryEnumStringType()
      : base(typeof (Country), 30)
  {
  }
  ...
}

Cool, cool, so far so good. This is about how much we'd need to do if we had no spaces to worry about. Unfortunately, if we used these two classes we made as is, the database would contain "United_States_of_America" instead of "United States of America". And that's no good... so how do we get around that?

Why, we need to override the GetValue and GetInstance methods, of course!

Let's plunk open the NHibernate source code and see what they have there now, to use as a guide. Ahh, there she is:

public virtual object GetInstance(object code)
{
  //code is an named constants defined for the enumeration.
  try
  {
    return StringToObject(code as string);
  }
  catch (ArgumentException ae)
  {
    throw new HibernateException(
      string.Format("Can't Parse {0} as {1}",
      code, ReturnedClass.Name), ae);
  }
}

public virtual object GetValue(object code)
{
  //code is an enum instance.
  return code == null ? string.Empty : code.ToString();
}

The comments aren't the greatest, but we can figure out what's going on. GetInstance converts a string in an enum instance, and GetValue converts an enum to a string.

With that in mind, let's get to work. First, let's put in some stubs in our CountryEnumStringType class:

// CountryEnumStringType.cs
class CountryEnumStringType : NHibernate.Type.EnumStringType
{
  // The 30 is the size of the field in the DB.
  public CountryEnumStringType()
      : base(typeof (Location), 30)
  {
  }

  ///
  /// Converts a string to an enum instance.
  ///
  public override object GetInstance(object code)
  {

  }

  ///
  /// Converts a enum instance to a string.
  ///
  public override object GetValue(object code)
  {

  }
}

Sweet. Now, our basic strategy here is this:

  • when NHibernate asks for an enum instance (and passes a string), we want to replace spaces in the string with underscores, and then find the corresponding enum instance.
  • when NHibernate asks for a string (and passes an enum instance), we want to convert the enum to a string, and then replace all underscores with spaces.

First, we modify GetInstance:

///
/// Converts a string to an enum instance.
///
public override object GetInstance(object code)
{
  // This is the string coming from the database, so it will
  // have spaces in it that we need to turn into underscores.
  var str = (string) code;

  // If the string is null, just pass it on, no need to replace
  // spaces with underscores.
  if (string.IsNullOrEmpty(str)) return StringToObject(str);
  else return StringToObject(str.Replace(" ", "_"));
}

In case you're wondering where the hell StringToObject came from, and what it does, I can tell you this:

  • It was used in the code for the EnumStringType class,
  • its name implies that it's used to convert a string into an object,
  • the context in which it is used supports the previous implication, and
  • if you really want to know what it does, read the code for the AbstractEnumType base class.

With that out of the way, let's move on to writing the code for the GetValue method. In this case we're looking at converting an enum instance into a string (that will eventually be stored in the database):

///
/// Converts a enum instance to a string.
///
public override object GetValue(object code)
{
  return code == null
    ? string.Empty
    : code.ToString().Replace('_', ' ');
}

The GetValue is pretty straight forward. We basically just tagged on the Replace method to replace all underscores with spaces.

That should about do it. To close, I'll give a sample usage of it:

// Order.cs
public class Order
{
  public virtual long Id { get; set; }
  ...
  public virtual Country Country { get; set; }
  ...
}

Then this would be the mapping:

<!-- Order.hbm.xml -->
<class name="Order" table="TblOrder">
  ...
  <property name="Country"
    type="WebApp.Domain.CountryEnumStringType, WebApp" />
  ...
</class>