Tuesday, December 19, 2006

Generic NHibernate Enum String Mapping

Today one of my coworkers mentioned all the little NHibernate enum mapping turd classes that were accumulating in our data access layer.  See this post for more details on how it works and why you would want to do this.  I wondered aloud if generics could be used to eliminate this waste of disk space.  A few minutes later, this is what we came up with.

public class GenericEnumMapper<TEnum> : EnumStringType
{
public GenericEnumMapper() : base(typeof(TEnum))
{
}
}

To use this class in your NHibernate mapping file, just use the following lovely .NET 2.0 generics syntax in your "type" attribute:

MyNamespaceB.GenericEnumMapper`1[[MyNamespaceA.MyEnum,
MyAssemblyA]], MyAssemblyB

No, that ` is not an apostrophe, it's a backtick.  It lives on the same key as ~ on my keyboard.


Here it is in a sample hbm.xml mapping file:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="MyNamespaceA.UserCredential, MyAssemblyA"
table="UserCredential" lazy="false">
<id name="Id" column="ID" type="Guid">
<generator class="guid.comb" />
</id>
<property name="UserId" />
<property name="CredentialType" column="CredentialTypeID"
type="MyNamespaceB.GenericEnumMapper`1[[MyNamespaceA.CredentialType,
MyAssemblyA]], MyAssemblyB"
/>
...
</class>
</hibernate-mapping>

Copy, paste, and replace CredentialType with your own enum type, and you're good to go!

17 comments:

Tim said...

Avast! I tried your example and even checked it twice. Keeps giving me an NHibernate.MappingException "could not interpret type..." Any ideas?

Oran Dennison said...

Works on my box! ;) Does the non-generic technique described here work? If not, the problem is somewhere in your project and it would be simplest to debug your problem using the non-generic solution first and then switch to the generic version once you figure it out. If the non-generic solution works, there may be something wrong with your generic syntax. You're using a backtick ` not a ' apostrophe right? And you're double-nesting your brackets like this, [[]]? Try doing a copy-paste-modify to make extra sure.

Brian said...

Great post. Worked like a charm.

MatFiz said...

Thank you for the solution!
It's ugly but it shows the right direction.

Matthew Brubaker said...

Oran, thanks for the insight. I have taken what you have done here along with additional information to create a transparent NHibernate enum mapper. It will allow you to map complex (special characters, puctuation, etc...) from your enum into a database. You can find it here.

http://geekswithblogs.net/ResultantCode/archive/2008/09/11/enums-and-nhibernate--taking-the-logic-out-of-the.aspx

Anonymous said...

First, nice post! There are many reason to include the text of enums. It is amazing how performance is always emphasized when many, many scenarios would have virtually no performance impact for string versus int storage. How about reports or other contexts where the values are read-back? With string values, reports or exports are complete as-is. With enums, some form of lookup and conversion must be completed.

BTW: Have you noticed that your "What I read" links are in a box placed directly in the middle of your post and it covers the most important line of code? That is not good... it is like a very seriously annoying advertisement that won't go away.

Anonymous said...

Why are there references to AssemblyA and AssemblyB?

Oran Dennison said...

GenericEnumMapper may be defined in a different assembly than MyEnum. So in the example given, MyEnum lives in AssemblyA while GenericEnumMapper lives in AssemblyB.

jasonsirota said...

If you're using NHibernate.Mapping.Attributes this becomes even easier because you don't have to much with the .NET funky generics representation syntax, the reflection takes care of it:

[Property(TypeType=typeof(GenericEnumMapper&;t;ResponseType>))]
public virtual ResponseType Response { get; set; }

Which generates the following mapping:

<property name="Response" type="Namespace.GenericEnumMapper`1[[Namespace.ResponseType, Namespace.Assembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Assembly" />

Anonymous said...

Forgive what may be a silly question, but at what point do changes made tot he enum in code get persisted to the database?

Oran Dennison said...

I would avoid changing the enum. But feel free to try it and find out... :-) Remember, there's no such thing as magic.

Reddy said...

It helped me today.. Thank you for the good work.

Reddy.

Anonymous said...

Thanks for contributing this. I couldn't have found this without your blog-entry!

PaRa said...

golden .......

ncloud said...

In the latest version of NHibernate (2.1.2) you can actually do this:


type="NHibernate.Type.EnumStringType`1[[MyAssembly.MyClass, MyAssembly]], NHibernate"


Thanks for the helpful post.

ncloud said...

In my previous comment it should be MyAssembly.MyEnum (not MyClass).

Anonymous said...

some mor information would be great