Persisting enumeration classes with NHibernate

As part of my “Crafting Wicked Domain Models” talk, I walk through the concept of enumeration classes, yanked from Java and on Jon Skeet’s list of biggest C# mistakes (or missing features). In my talk, I leave out how to bridge the gap from your domain model to an ORM, simply because it’s just out of scope for that talk to address persistence concerns. Besides, the ORM I use these days to persist relational domain models (NHibernate) handles all the crazy cases an more, so I don’t feel like looking at anything else.

But what I leave out of a talk, I can certainly blog about! Suppose we have one of our enumeration classes (available here on NuGet):

public class Color : Enumeration<Color>
{
    private Color(int value, string displayName) 
        : base(value, displayName)
    {
    }

    public static readonly Color Red 
        = new Color(1, "Red");
    public static readonly Color Blue 
        = new Color(2, "Blue");
}

When it comes time to persisting this enumeration class, we want to make sure that the database schema uses the integer value as what gets persisted. When it writes, we want the value to persist, and when it reads, we want the correct enumeration value (Red/Blue) to be hydrated.

To do this in NHibernate, we’ll first need a custom type:

public class EnumerationType<T> : PrimitiveType where T : Enumeration<T>
{
    public EnumerationType()
        : base(new SqlType(DbType.Int32))
    {
    }

    public override object Get(IDataReader rs, int index)
    {
        var o = rs[index];
        var value = Convert.ToInt32(o);
        return Enumeration<T>.FromInt32(value);
    }

    public override object Get(IDataReader rs, string name)
    {
        var ordinal = rs.GetOrdinal(name);
        return Get(rs, ordinal);
    }

    public override Type ReturnedClass
    {
        get { return typeof(T); }
    }

    public override object FromStringValue(string xml)
    {
        return int.Parse(xml);
    }

    public override string Name
    {
        get { return "enumeration"; }
    }

    public override void Set(IDbCommand cmd, object value, int index)
    {
        var parameter = (IDataParameter)cmd.Parameters[index];

        var val = (Enumeration<T>)value;

        parameter.Value = val.Value;
    }

    public override string ObjectToSQLString(object value, Dialect dialect)
    {
        return value.ToString();
    }

    public override Type PrimitiveClass
    {
        get { return typeof(int); }
    }

    public override object DefaultValue
    {
        get { return 0; }
    }
}

This class is the bridge between our ORM (NHibernate) and our enumeration class. NHibernate is fantastic in its ability to provide easy ways to bridge to value objects. Value objects help avoid primitive obsession, but is only useful if you can actually use them when you’re mapping to your persistence layer.

To instruct NHibernate to use our custom types when reading/writing, the easiest way to do so is with a Fluent NHibernate convention:

public class EnumerationTypeConvention : IPropertyConvention, IPropertyConventionAcceptance
{
    private static readonly Type OpenType = typeof(EnumerationType<>);

    public void Apply(IPropertyInstance instance)
    {
        var closedType = OpenType.MakeGenericType(instance.Property.PropertyType);

        instance.CustomType(closedType);
    }

    public void Accept(IAcceptanceCriteria<IPropertyInspector> criteria)
    {
        criteria.Expect(x => IsEnumerationType(x.Property.PropertyType));
    }

    private bool IsEnumerationType(Type type)
    {
        return GetTypeHierarchy(type)
            .Where(t => t.IsGenericType)
            .Select(t => t.GetGenericTypeDefinition())
            .Any(t => t == typeof(Enumeration<>));
    }

    private IEnumerable<Type> GetTypeHierarchy(Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }
}

This convention walks the type hierarchy for each property type given, and checks to see if the property type’s type hierarchy is a generic type that closes the open generic type of our enumeration class. Finally, we just need to hook our convention up to Fluent NHibernate, but that really depends on how we have Fluent NHibernate hooked up. We don’t have to use Fluent NHibernate to hook up our custom user type, but it’s much easier this way.

With NHibernate, we get the benefits of using Java-style enumeration classes, and have it seamlessly plug in to our persistence layer, which is the whole point of ORMs, right?

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in Domain-Driven Design, NHibernate. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Anonymous

    Cool. Why just not to create table for enumeration class?

    • Anonymous

      We do sometimes – but that has its drawbacks. It requires extra queries and sometimes joins. We limit those cases to ones where we have user-supplied information, rather than static information.

      We have pushed out DB tables from our enumeration classes, too. Making our C# code the reference and updating DB tables as needed (for SQL-centric reports etc).

  • Daniel Marbach

    Because it’s NOT an entity :)

  • Anonymous

    Hi Jimmy, (have just watched your video)

    if you’re into this kind of thing you should take a look at my Harden library ( https://github.com/mcintyre321/Harden ) – it lets you add validation rules and guard rules to objects cleanly, very much keeping logic out of the Views and Services, and keeping it in the domain objects.

    You write can rules like this:

    public virtual string Email { get; set; }

    public bool AllowEmail()
    {
    return Context.CurrentUser.IsAdmin || Context.CurrentUser == this;
    //only admins can see or change other peoples emails
    }

    and when someone tries to set the property, the HardenInterceptor checks the rule.

    Normally have to put this rule into the setter, but this way you can also interrogate the rule form other code, for example when deciding whether or not to show the email field in the form.

    It has a totally pluggable extension system, so you can define rules in standalone  classes, or in attributes or anything really.

    • Anonymous

      Cool, I’ll take a look!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1097

  • Sandor Drieënhuizen

    I like to use the following simple solution to having enums mapped as integers, using only a small FluentNHibernate mapping convention:

        public class EnumConvention : IUserTypeConvention
        {
            public void Accept(IAcceptanceCriteria criteria)
            {
                criteria.Expect(n => n.Property.PropertyType.IsEnum);
            }

            public void Apply(IPropertyInstance instance)
            {
                // Map using the integral representation of the enum value.
                instance.CustomType(instance.Property.PropertyType);
            }
        }
     
    Further, it’s important to assign explicit integral values to our enum, to ensure that the integral representation of the enum values stays the same when a new value is added for example:

    enum MyEnum
    {
         FirstValue = 1,
         SecondValue = 2
    }

    Ofcourse, this is no more safe than enums are in C# but it suits me well.

  • http://hammerproject.com/ matt kocaj

    And the HBM mapping example? (as much as I’d love the freedom to be using Fluent on this project)

    • Anonymous

      Ugly, no?

      Why no Fluent NHibernate? It’s really easy to migrate/have both in one project.

      • Никита Говоров

        Probably better to use ‘typedef’ to reduce count of full qualified type name strings:

         

  • http://hammerproject.com/ matt kocaj

    And this does or does not create a lookup table for the 
    TEnumeration  items? Or is it the same as my SO example (http://stackoverflow.com/a/10426960/56145), where it simply tells NHibernate how to link the index/value field?

  • cbp

    Just a question – why did you use PrimitiveType instead of IUserType?

    • Anonymous

      Copy-Paste from other projects, really. It wasn’t an intentional decision one way or the other, other than it seemed that PrimitiveType had less to implement out of the box than IUserType.

  • Carlos Alberto Costa Beppler

    Hello, I have to map soma classes that are almost the same of this.

    The difference is that in my case I have some prefedined values, but the user can add more values to the database (the enumeration is an entity).

    There are any way to do this using NH?

    • Anonymous

      Yep – but the easiest way is to have all of the values in the DB, and just model it such that some are “built in” and can’t be deleted/modified through your app, and some are custom. We usually use a “System” or “BuiltIn” bit flag to indicate this on that table.

  • http://twitter.com/jbasilio Jose Basilio

    How do I use Fluent NHibernate to map this enumeration as a string in SQL? In other words, using your example, how do I get it to persist as “Red” or “Blue” instead of 1 or 2?

    • Mattias Jöraas

      I also need a way to map my custom enumeration classas as string, how do i do this. In some cases we have the data mapped as int and some as string.

  • Antonio Castro Jr

    Jimmy,

    Does it work with Entity Framework 4.1 (Code First) ?

  • Thiago Saadeh

    I straight copy pasted your samples and hooked it up to FH the conventions and ran it to see where we would go from there, this is what I got:

    The entity ‘Color’ doesn’t have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id).

  • Jamie Wijaya

    It’s kind for mapping a “descriptor pattern” class?

    http://www.devx.com/enterprise/Article/28246/0/page/3

  • oguzh4n

    how can I map enumeration on Nhibernate 3 (not fluent nhibernate)