Uno UserType generico per gli Enums

NHibernate tende a mappare gli Enums come stringhe nel database; questo a volte può non essere un problema, ma personalmente preferisco memorizzarli in un intero, o ancora meglio un tynint, infatti con questo tipo di formato si ha un netto risparmio di spazio, ma anche un vantaggio per eventuali query.

NHibernate permette di cambiare il modo di persistere un tipo di oggetto del nostro domain model sul database tramite delle apposite classi; tradotto più semplicemente permette di avere una classe sulla propria entity e un semplice campo nel database (ad esempio un intero o un varchar), come potrebbe essere il CultureInfo che, in una classe come quella mostrata di seguito, è un oggetto, mentre sul database una semplice string tipo “en-US”.

public class Comment : EntityBase
{
    public virtual Item Item { get; set; }
    public virtual DateTime CommentDate { get; set; }
    public virtual string Message { get; set; }
    public virtual string Name { get; set; }
    public virtual string Email { get; set; }
    public virtual bool Notify { get; set; }
    public virtual string WebSite { get; set; }
    public virtual bool Approved { get; set; }
    public virtual bool IsSpam { get; set; }
    public virtual System.Globalization.CultureInfo Culture { get; set; }
}

Lo stesso approccio può essere utilizzato per gli Enums, quindi un enum come il seguente:

public enum BlogRollFriendType : short 
{
    Nothing = 0,
    Contact = 1,
    Friend = 2,
    Acquaintance = 3
}

può essere persistito come un tinyint sul database grazie ad uno UserType come quello mostrato di seguito:

public class GenericEnumMapper<T> : IUserType
{
    #region IUserType Members

    public new bool Equals(object x, object y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x == null || y == null) return false;
        return x.Equals(y);
    }

    public int GetHashCode(object x)
    {
        return x.GetHashCode();
    }

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        object obj = NHibernateUtil.Int16.NullSafeGet(rs, names[0]);

        if (obj == null)
            return null;

        return (T) obj;
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        if (value == null)
            ((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
        else
            ((IDataParameter) cmd.Parameters[index]).Value = (short) value;
    }

    public object DeepCopy(object value)
    {
        return value;
    }

    public object Replace(object original, object target, object owner)
    {
        return original;
    }

    public object Assemble(object cached, object owner)
    {
        return cached;
    }

    public object Disassemble(object value)
    {
        return value;
    }

    public SqlType[] SqlTypes
    {
        get { return new[] {new SqlType(DbType.Int16)}; }
    }

    public Type ReturnedType
    {
        get { return typeof (T); }
    }

    public bool IsMutable
    {
        get { return false; }
    }

    #endregion
}

L’implementazione è piuttosto semplice, l’unica accortezza che bisogna avere per poter persistere l’enum come tinyint è specificare lo usertype nel mapping, come mostrato di seguito:

public BlogRoll()
{
    Table("BlogsRoll");
    DynamicUpdate();
    LazyLoad();
    Cache.NonStrictReadWrite();

    Id(x => x.ID)
        .GeneratedBy.Identity();

    Map(x => x.Name)
        .Not.Nullable()
        .Length(100);

    Map(x => x.Link)
        .Not.Nullable()
        .Length(100);

    Map(x => x.IsMyBlog);
    Map(x => x.FriendType)
        .CustomType(typeof(Dexter.NHibernate.Helpers.UserTypes.GenericEnumMapper<BlogRollFriendType>));

    Map(x => x.GeographicalType)
        .CustomType(typeof (Dexter.NHibernate.Helpers.UserTypes.GenericEnumMapper<BlogRollGeographicalType>));
}

Lo screenshot seguente mostra la struttura della tabella:
table.


Comments