среда, 25 июля 2012 г.

Fluent NHibernate: маппинг перечислений(Enum)

Если поискать, то можно найти достаточно много способов маппинга перечислений в NHibernate, я для себя выбрал вариант, при котором значения перечисления сохраняются, как строки в БД, используя созданный пользовательский тип данных.
Рассмотрим сущность Продукт, которая может иметь несколько полей, и одно из которых - это количество продукта, имеющее определенную для каждого Продукта единицу измерения. Например, молоко в литрах, сахар в граммах, яйца в штуках и т.д..
В коде, это будет выглядеть так:

public enum UnitType

{

    Piece,

    Milliliter,

    Gram

}

class Product : EntityBase

{

   /**~~~**/

   public virtual UnitType UnitType { get; set; }

   /**~~~**/

}

Для осуществления маппинга перечисления, во-первых, необходимо реализовать интерфейс IUserType:

public class UnitTypeEnumMap : IUserType
{
    public bool Equals(object x, object y)
    {
        return object.Equals(x, y);
    }

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

    public object NullSafeGet(IDataReader rs, string[] names, object owner)
    {
        object r = rs[names[0]];
        var value = (string)r;

        if (string.IsNullOrEmpty(value))
            throw new Exception("Invalid Unit Type");

        switch (value)
        {
            case "P":
                return UnitType.Piece;
            case "Ml":
                return UnitType.Milliliter;
            case "G":
                return UnitType.Gram;

            default:
                throw new Exception("Invalid Unit Type");
        }
    }

    public void NullSafeSet(IDbCommand cmd, object value, int index)
    {
        object paramVal = 0;
        switch ((UnitType)value)
        {
            case UnitType.Piece:
                paramVal = "P";
                break;
            case UnitType.Milliliter: 
                paramVal = "Ml";
                break;
            case UnitType.Gram:
                paramVal = "G";
                break;
            default:
                throw new Exception("Invalid Unit Type");
        }
        var parameter = (IDataParameter)cmd.Parameters[index];
        parameter.Value = paramVal;
    }

    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 SqlType[] {new StringSqlType()}; }
    }

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

    public bool IsMutable
    {
        get { return false; }
    }
}


Во-вторых, при маппинге сущности Продукт используется свойство CustomType:
Map(x => x.UnitType).CustomType<UnitTypeEnumMap>();


Посмотрев на код становится ясно, что вся логика скрыта в 2-х функциях NullSafeGetNullSafeSet и свойстве public SqlType[] SqlTypes. 
Функция NullSafeGet реализует логику чтения пользовательского типа, учитывая возможные null значения, а функция NullSafeSet  - логику записи, соответственно.
Свойство SqlTypes отвечает за то, какой внутренний тип данных БД будет использоваться для хранения пользовательского типа. Возвращается массив значений, так как пользовательский тип может занимать не один столбец в таблице данных.