惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
Forbes - Security
Forbes - Security
Help Net Security
Help Net Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
TaoSecurity Blog
TaoSecurity Blog
IT之家
IT之家
Microsoft Azure Blog
Microsoft Azure Blog
云风的 BLOG
云风的 BLOG
博客园 - 司徒正美
B
Blog
阮一峰的网络日志
阮一峰的网络日志
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
The Register - Security
The Register - Security
美团技术团队
C
CERT Recently Published Vulnerability Notes
I
Intezer
C
Cybersecurity and Infrastructure Security Agency CISA
Google Online Security Blog
Google Online Security Blog
B
Blog RSS Feed
PCI Perspectives
PCI Perspectives
C
Cyber Attacks, Cyber Crime and Cyber Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
MyScale Blog
MyScale Blog
S
Securelist
Recorded Future
Recorded Future
Know Your Adversary
Know Your Adversary
Security Archives - TechRepublic
Security Archives - TechRepublic
M
MIT News - Artificial intelligence
C
Check Point Blog
T
Threat Research - Cisco Blogs
博客园 - Franky
P
Proofpoint News Feed
人人都是产品经理
人人都是产品经理
U
Unit 42
F
Fortinet All Blogs
S
Security @ Cisco Blogs
The GitHub Blog
The GitHub Blog
Apple Machine Learning Research
Apple Machine Learning Research
MongoDB | Blog
MongoDB | Blog
The Hacker News
The Hacker News
酷 壳 – CoolShell
酷 壳 – CoolShell
F
Full Disclosure
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
D
Docker
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
LINUX DO - 热门话题

博客园 - 1-2-3

[日记]超解压的几句话 【效率】专为Win7系统设计的极简番茄计时器 - MiniPomodoro (附源码) AutoMapper 最佳实践 三国杀的10个人生感悟 [Oracle](不会的是三炮)把状态列表作为存储过程参数这件小事 [激励机制]浅谈内部竞争——如何让你的员工玩命干活? PC使用网线上网的条件下,通过PC的Wifi共享提供手机上网教程 [非技术]滑雪恐怖故事 Oracle 数据库的逻辑迁移 VMware Linux 下安装 Oracle 11gR2 RAC [javascript]图解+注释版 Ext.extend() Oracle 数据库隔离级别,特性,问题和解决方法 [双板滑雪]教学视频和技术贴精选 [日记]夜滑 [推荐]番茄工作法——专治拖延症、精神涣散、再要五分钟综合症 建模心法(3) 模型的演进——保持简单和弹性的3个建议 [日记]游长白遇梅花,植物大战僵尸 ExtJs4 与 SASS 的亲密接触——使用 SASS 自定义主题 [NHibernate] Guid 作主键速度超慢的背后
配置NHibernate将枚举保存为Oracle数据库中的字符串
1-2-3 · 2015-11-23 · via 博客园 - 1-2-3

假设有这样一个枚举:

/// <summary>
/// 字典项类型
/// </summary>
public enum DicItemType
{
    [EnumDescription("程序使用")]
    Program = 0,

    [EnumDescription("用户自定义")]
    Custom = 1
}

NHibernate默认是映射为数据库中的数字类型,也就是0或者1。当我们使用数据库管理工具(例如PLSql/Developer)直接浏览数据库里的数据时,看到的一排又一排的0或者1,可读性不太好,总是要再去找枚举定义看0代表的是什么,1代表的又是什么。当使用存储过程写一些复杂查询时,也会写 CASE WHEN ITEM_TYPE = 1 THEN ... 或者 WHERE ITEM_TYPE = 1 这样包含了神奇数字的语句,可读性同样不好。虽然可以在后面跟上注释,也还是很烦。能不能让NHibernate把枚举保存为数据库里的字符串呢?例如不是保存0或者1,而是保存为 "Program" 或者 "Custom"。答案是肯定的。NHibernate提供了自定义类型的机制,通过将枚举映射为一个自定义类型,就可以自定义从数据库中读取枚举数据和保存枚举到数据库中的操作。下面详细描述一下实现方法。环境:.Net Framework 4.0,Oracle 11g R2,NHibernate 3.3.1.4000,FluentNHibernate 1.3.0.733,使用Oracle的64位ODP Oracle.DataAccess 4.112.3.0。

新增自定义类型

首先,需要新增一个自定义类型 NullableEnumCusType,它实现 IUserType 接口。这个自定义类型告诉NHibernate要把枚举保存为数据库中的 NHibernateUtil.AnsiString.SqlType 类型,以及如何从数据库中读取和保存枚举值(经由 NullSafeGet() 和 NullSafeSet() 方法)。

using System;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;

namespace Zen.Framework.Data
{
    public class EnumCusType<TEnum> : IUserType where TEnum:struct 
    {
        public object Assemble(object cached, object owner)
        {
            return DeepCopy(cached);
        }

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

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

        public bool Equals(object x, object y)
        {
            return x.Equals(y); // 由于装箱后 x == y 会总是返回 false, 所以要使用 Equals() 
        }

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

        public bool IsMutable
        {
            get { return true; }
        }

        public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
        {
            return Enum.Parse(typeof(TEnum), NHibernate.NHibernateUtil.AnsiString.NullSafeGet(rs, names[0]).ToString());
        }

        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            NHibernate.NHibernateUtil.AnsiString.NullSafeSet(cmd, value.ToString(), index);
        }

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

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

        public NHibernate.SqlTypes.SqlType[] SqlTypes
        {
            get { return new SqlType[] { NHibernateUtil.AnsiString.SqlType }; }
        }
    }
}

EnumCusType

使用自定义类型


在实体的枚举属性映射时,要指定上面新增的自定义枚举类型。这样就已经可以实现枚举与数据库字符串之间的映射和转换了。

Map(t => t.ItemType, "ITEM_TYPE").CustomType<EnumCusType<DicItemType>>();
/// <summary>
/// 字典项类型2
/// </summary>
public virtual DicItemType? ItemType2 { get; set; }

需要在增加一个针对可为空枚举的自定义类型:

using System;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;

namespace Zen.Framework.Data
{
    public class NullableEnumCusType<TEnum> : IUserType
    {
        public object Assemble(object cached, object owner)
        {
            return DeepCopy(cached);
        }

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

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

        public bool Equals(object x, object y)
        {
            if (x == null)
            {
                return x == y;
            }
            else
            {
                return x.Equals(y); // 由于装箱后 x == y 会总是返回 false, 所以要使用 Equals() 
            }
        }

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

        public bool IsMutable
        {
            get { return true; }
        }

        public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
        {
            var v = NHibernate.NHibernateUtil.AnsiString.NullSafeGet(rs, names[0]);
            if (v == null)
            {
                return null;
            }
            else
            {
                return Enum.Parse(typeof(TEnum).GetGenericArguments()[0], v.ToString());    
            }
        }

        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            NHibernate.NHibernateUtil.AnsiString.NullSafeSet(cmd, (value == null? null : value.ToString()), index);
        }

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

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

        public NHibernate.SqlTypes.SqlType[] SqlTypes
        {
            get { return new SqlType[] { NHibernateUtil.AnsiString.SqlType }; }
        }
    }
}

NullableEnumCusType

然后把可为空类型的实体属性映射为可为空的自定义枚举类型,这样可为空的枚举也没问题了。

Map(t => t.ItemType2, "ITEM_TYPE2").CustomType<NullableEnumCusType<DicItemType>>();
 

使用 EnumConvention 统一配置自定义枚举类型

之前,我们是可以在 EnumConvention 中对枚举类型进行统一配置的,也就是说,在实体映射的时候,不需要区分枚举和普通的字符型以及数字型属性,例如可以这样:

/// <summary>
/// 字典类别
/// </summary>
public class DicItemEntityMap : InputItemMap<DicItem>
{
    public DicItemEntityMap()
    {
        Table("SYS_DIC_ITEM");
        Id(t => t.Id, "DIC_ITEM_ID");
        Map(t => t.IsStop, "IS_STOP");
        Map(t => t.ItemType, "ITEM_TYPE");
        Map(t => t.ItemType2, "ITEM_TYPE2");
        References(t => t.Category, "DIC_CATEGORY_ID");
    }
}

这是因为 FluentNHibernate 提供了 Convention  机制可以对各种类型进行统一配置。例如只要实现这样一个 EnumConvention 就可以对所有枚举统一配置:

public class EnumConvention : IUserTypeConvention
{
    public void Accept(FluentNHibernate.Conventions.AcceptanceCriteria.IAcceptanceCriteria<FluentNHibernate.Conventions.Inspections.IPropertyInspector> criteria)
    {
        // 匹配枚举或可为空枚举
        criteria.Expect(x => x.Property.PropertyType.IsEnum
                             ||
                             (x.Property.PropertyType.Name == "Nullable`1" &&
                              x.Property.PropertyType.GetGenericArguments().Length > 0 &&
                              x.Property.PropertyType.GetGenericArguments()[0].IsEnum));
    }

    public void Apply(FluentNHibernate.Conventions.Instances.IPropertyInstance instance)
    {
        instance.CustomType(instance.Property.PropertyType);
    }
}

EnumConvention

能不能把上面的 EnumConvention 改造一下,适配新增的 EnumCusType 和 NullableEnumCusType 呢?这样就不用每次都写 ” Map(t => t.ItemType, "ITEM_TYPE").CustomType<EnumCusType<DicItemType>>() “ 这样烦人的配置了。

但是你马上就会发现 EnumCusType 是非常坑爹的泛型,在 EnumConvention 里没法直接调用 instance.CustomType() 进行配置。好在微软提供了非常强大的Emit,动态生成一个泛型类型非常轻松:

public class EnumConvention : IUserTypeConvention
{
    public void Accept(FluentNHibernate.Conventions.AcceptanceCriteria.IAcceptanceCriteria<FluentNHibernate.Conventions.Inspections.IPropertyInspector> criteria)
    {
        // 匹配枚举或可为空枚举
        criteria.Expect(x => x.Property.PropertyType.IsEnum
                             ||
                             (x.Property.PropertyType.Name == "Nullable`1" &&
                              x.Property.PropertyType.GetGenericArguments().Length > 0 &&
                              x.Property.PropertyType.GetGenericArguments()[0].IsEnum));
    }

    public void Apply(FluentNHibernate.Conventions.Instances.IPropertyInstance instance)
    {
        //instance.CustomType(instance.Property.PropertyType);
        if (instance.Property.PropertyType.Name == "Nullable`1")
        {
            // 转换为可空枚举自定义类型
            Type t = TypeBuilder.GetType(
                string.Format("Zen.Framework.Data.NullableEnumCusType`1[[{0}]]",
                              instance.Property.PropertyType.AssemblyQualifiedName));

            instance.CustomType(t);
        }
        else
        {
            // 转换为非可空枚举自定义类型
            Type t = TypeBuilder.GetType(
                string.Format("Zen.Framework.Data.EnumCusType`1[[{0}]]",
                              instance.Property.PropertyType.AssemblyQualifiedName));

            instance.CustomType(t);
        }
    }
}

使用Emit配置泛型自定义类型