Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle exception from nHibernate EnumStringType when enum values don't match string values

Tags:

c#

nhibernate

Her is my situation where the enum values are stored as string in the Db. While retrieving I am getting an exception trying to convert string to enum using mytype that has the base class as EnumStringType.

Here is the error I am getting:

NHibernate.HibernateException : Can't Parse Enum4 as MyEnum

For example: The value coming from database is: "Enum4"

Valid enum values as per the code for MyEnum are:

Enum1 Enum2 Enum3

Somehow Enum4 got introduced in the Db before the code has accommodated the change. (I know crazy stuff happens)

The exception is normal because my Enum does not have this value coming from the database. But I don't want the user to get an exception. Instead default to the first value. (I agree this is not OK for some cases, but it is prevents exception which is more severe in my case)

If I am correct, GetInstance is the method that does this conversion from string to enum. Is there a TryGetXXXX of some sort to overcome this issue or how to solve it?

Thanks for your time!

Here is the Enum code I am playing with to address this issue:

public class EnumMappingBase : EnumStringType
{
    public EnumMappingBase(Type type)
        :base(type)
    { 

    }

    public override object GetInstance(object code)
    {
        return base.GetInstance(code); // Here is where I get the exception. 
        // I am thinking this is where capturing the exception and defaulting must happen. 
        // I wish I had a TryGetInstance() here or may be it is there and I am not aware.
    }

    public override object GetValue(object code)
    {
        return base.GetValue(code);
    }

}

public enum MyEnum
{
    Enum1,
    Enum2,
    Enum3
}

public class MyEnumType : EnumMappingBase
{
    public MyEnumType()
        : base(typeof(MyEnum))
    {

    }
}
like image 965
isakavis Avatar asked Dec 20 '13 00:12

isakavis


2 Answers

Try to override GetInstance() in MyEnumType as follows:

public class MyEnumType : EnumMappingBase
{
    public MyEnumType()
        : base(typeof(MyEnum))
    {}

    public override object GetInstance(object code)
    {
        // Set the desired default value
        MyEnum instanceValue = MyEnum.Enum1;
        Enum.TryParse(code, true, out instanceValue);

        return instanceValue;    
    }
}
like image 143
kay.herzam Avatar answered Oct 14 '22 09:10

kay.herzam


I ran into the same problem today, and kay.herzams answer helped be out to create this class which can be used for any enumeration type.

It's a little bit more generic and flexible, so I thought I'd share it for anyone looking for a generic solution for this.

https://gist.github.com/flopes89/f6c4a079ee3b82c7a1df

using System;
using System.Reflection;
using log4net;
using NHibernate.Type;

/// <summary>
/// This class serves as a helper class to properly en- and decode enum values to/from strings
/// using hibernate. It is a generic class that can be used with any enumeration type and is designed
/// to replace the standard NHibernate.EnumStringType entirely.
/// 
/// This class should be used whenever an enumeration is consisted via NHibernate, because it has a failsafe
/// decoding of enumeration values from the database by defaulting them back to the given default value
/// when an unknown enumeration value is found in the database, which the default NHibernate.EnumStrinType does not
/// </summary>
/// <typeparam name="T">The enumeration type to use</typeparam>
public class EnumStringType<T> : EnumStringType where T : struct
{
    private static ILog _logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
    private T _defaultValue;

    /// <summary>
    /// Create the type
    /// </summary>
    /// <param name="defaultValue_">Value to fallback to if an unknown enum value is found</param>
    public EnumStringType(T defaultValue_)
        : base(typeof(T))
    {
        _defaultValue = defaultValue_;
    }

    /// <summary>
    /// Get the value of the given code
    /// </summary>
    /// <param name="code_">The code will be decoded using Enum.TryParse with the Type of this EnumStringType instance</param>
    /// <returns>Either the defaultValue of this instance (logged with a warning) or the value of the code</returns>
    public override object GetInstance(object code_)
    {
        T instanceValue = _defaultValue;

        if (code_ != null && Enum.TryParse<T>(code_.ToString(), true, out instanceValue)) {
            _logger.Debug("Decoded [" + typeof(T) + "] enum value [" + instanceValue + "]");
        }
        else {
            _logger.Warn("Unknown [" + typeof(T) + "] enum value [" + code_ + "] found, defaulting to [" + instanceValue + "]");
        }

        return instanceValue;
    }
}

Usage example:

public enum Permission
{
    NULL,
    EDIT
}

public class PermissionStringType : EnumStringType<Permission>
{
    public PermissionStringType()
        : base(Permission.NULL)
    {

    }
}

And the mapping can be done by:

<set name="Permissions" table="permissions" lazy="true">
    <key column="role"/>
    <element column="name" type="My.Namespace.PermissionEnumStringType,MyAssemblyName"/>
</set>
like image 1
F.P Avatar answered Oct 14 '22 09:10

F.P