Everyone knows the benefits of using enums in code. However, when you use NHibernate to store those enums to a database, all you get are the numeric values those enums represent. I wanted a way to take the logic I was using to make the numeric values in the database into a business value a user could make use of.
With this problem in mind, I began looking for a solution. My first thought was to simply override ToString, but a) that can’t be done, and b) even if it could, it wouldn’t allow round tripping of the data. NHibernate makes available a class called EnumStringType which would allow me to round trip the data successfully, but is ultimately limited to using the ToString function of the enum. This poses some limitations when the value you want to be placed in the database is more complex than the enum allows. For instance, you couldn’t make an make an enum value that has special characters in it. This led me to two posts, Jeffrey Palermo and abhinaba both provided information which I used as the basis for my solution.
By combining the idea of using EnumStringType with the idea of keeping the String I wanted to show up in the database as an attribute of the enum’s value I came to the following solution. I also used an extension of Jeff Palermo’s idea posted by Oran Dennison to make the entire functionality generic enough to be able to be used for any enum. All that needs to be done is drop the following two classes into your project and then setting up your NHibernate mappings (see below for an example).
My extension of the EnumStringType:
Imports System
Imports System.Data
Imports System.Reflection
Imports NHibernate.Type
Public Class GenericEnumMapper(Of TEnum)
Inherits EnumStringType
Public Sub New()
MyBase.New(GetType(TEnum))
End Sub
'Get the enum corresponding to the value in the database
Public Overrides Function GetInstance(ByVal code As Object) As Object
Dim infos As FieldInfo() = ReturnedClass.GetFields()
If Not infos Is Nothing AndAlso infos.Length > 0 Then
For Each fieldInfo As FieldInfo In infos
Dim attributes As Object() = fieldInfo.GetCustomAttributes(GetType(BusinessDescriptionAttribute), False)
If Not attributes Is Nothing AndAlso attributes.Length > 0 Then
Dim attribute As BusinessDescriptionAttribute = TryCast(attributes(0), BusinessDescriptionAttribute)
If Not attribute Is Nothing Then
Dim description As String = attribute.Description
If description = code.ToString Then
Return System.Enum.Parse(ReturnedClass, fieldInfo.Name, True)
End If
End If
End If
Next
End If
Return MyBase.GetInstance(code)
End Function
'Set the value to be stored in the database
Public Overrides Sub [Set](ByVal cmd As IDbCommand, ByVal value As Object, ByVal index As Integer)
Dim parameter As IDataParameter = TryCast(cmd.Parameters(index), IDataParameter)
parameter.Value = DBNull.Value
If Not value Is Nothing Then
parameter.Value = System.Enum.Format(ReturnedClass, value, "G")
Dim infos As FieldInfo() = ReturnedClass.GetFields()
For Each fieldInfo As FieldInfo In infos
Dim attributes As Object() = fieldInfo.GetCustomAttributes(GetType(BusinessDescriptionAttribute), False)
If Not attributes Is Nothing AndAlso attributes.Length > 0 Then
Dim attribute As BusinessDescriptionAttribute = TryCast(attributes(0), BusinessDescriptionAttribute)
If Not attribute Is Nothing Then
If fieldInfo.Name = value.ToString Then
Dim description As String = attribute.Description
parameter.Value = description
End If
End If
End If
Next
End If
End Sub
End Class
The Attribute definition used to mark the values of an enum:
Imports System
Public Class BusinessDescriptionAttribute
Inherits Attribute
Private _description As String
Public Sub New(ByVal description As String)
_description = description
End Sub
Public ReadOnly Property Description() As String
Get
Return _description
End Get
End Property
End Class
An example of the NHibernate mappings:
<property name="Property" column="Column" type="SomePackage.GenericEnumMapper`1[[SomePackage.SomeEnum, SomePackage]], SomePackage" update="true" access="field.camelcase-underscore"/>
An example of how to use the attributes with the enum:
Public Enum SomeEnum
'You don’t have to put a BusinessDescription attribute on every value
UNKNOWN
<BusinessDescription("This value is special")> _
AValue
<BusinessDescription("This value is not quite as special")> _
AnotherValue
<BusinessDescription("Actually, this value is the most special…>.>")> _
NoValueHere
End Enum