配置NHibernate将枚举保存为Oracle数据库中的字符串

假设有这样一个枚举:

/// <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配置泛型自定义类型

时间: 2024-08-24 10:39:34

配置NHibernate将枚举保存为Oracle数据库中的字符串的相关文章

如何处理Oracle数据库中的坏块问题

本文主要介绍如何去处理在Oracle数据库中出现坏块的问题,对于坏块产生在不同的对象上,处理的方法会有所不同,本文将大致对这些方法做一些介绍.因为数据库运行时间长了,由于硬件设备的老化,出现坏块的几率会越来越大,因此,做为一个DBA,怎么去解决数据库出现的坏块问题就成了一个重要的议题了. 一:什么是数据库的坏块   首先我们来大概看一下数据库块的格式和结构 数据库的数据块有固定的格式和结构,分三层:cache layer,transaction layer,data layer.在我们对数据块进

给Oracle数据库中CLOB字段插入空值

遇到往ORACLE数据库中插入数据时总是报ORA-01084 invalid argument in OCI call错误,经分析是因为表中的一个字段类型为CLOB,并且可为空,当在给该字段插入空值时引发的该错误.后来判断是否为空值,如果为空值使用DBNull.Value,以此解决了该问题. Null 指的是无效的对象引用:而 DBNull 是一个类, DBNull.Value 是它唯一的实例 .DBNull 的实例 DBNull.Value是数据库表中的空数据在 .Net 代码中的表现形式.我

EXCEL表数据导入到ORACLE数据库中

将EXCEL表导入ORACLE数据库中 一.建立表 1.建立表 2.查询表 select * from yy; 二.导入程序 在excel中找到需要导入的数据 2.将excel中的数据另存为文本文件(有制表符分割的) 3.在pl*sql中选择tools-->text importer,在出现的窗口中选择"Data from Textfile",然后再选择"Open data file", 在弹出的文件选择框中选中保存有数据的文本文件,此时将会看到data fr

Oracle数据库中的blob类型解析

Oracle的Blob字段比较特殊,他比long字段的性能要好很多,可以用来保存例如图片之类的二进制数据. 写入Blob字段和写入其它类型字段的方式非常不同,因为Blob自身有一个cursor,你必须使用cursor对blob进行操作,因而你在写入Blob之前,必须获得cursor才能进行写入,那么如何获得Blob的cursor呢? 这需要你先插入一个empty的blob,这将创建一个blob的cursor,然后你再把这个empty的blob的cursor用select查询出来,这样通过两步操作

Oracle数据库中调用Java类开发存储过程、函数的方法

Oracle数据库中调用Java类开发存储过程.函数的方法 时间:2014年12月24日  浏览:5538次 oracle数据库的开发非常灵活,不仅支持最基本的SQL,而且还提供了独有的PL/SQL,除此之外,还可以用时下最流行的编程语言Java来做开发.随着对oracle的了解越来越多,越来越禁不住oracle的诱惑,oracle技术真的是一门很有趣的学问.之前,我在博客中总结了挺多有关SQL.PL/SQL的,但是对于oracle数据库中Java类的调用却没有总结,也是因为之前不太会,这会儿总

Oracle数据库中闪回恢复的详细分析

Oracle9i开始提供闪回查询,以便能在需要的时候查到过去某个时刻的一致性数据,这是通过Undo实现的.这个功能有很大的限制,就是相关事务的undo不能被覆盖,否则就无力回天了.oracle10g大大的增强了闪回查询的功能,并且提供了将整个数据库回退到过去某个时刻的能力,这是通过引入一种新的flashback log实现的. flashback log有点类似redo log,只不过redo log将数据库往前滚,flashback log则将Oracle数据库往后滚.为了保存管理和备份恢复相

详解大数据采集引擎之Sqoop&amp;采集oracle数据库中的数据

欢迎关注大数据和人工智能技术文章发布的微信公众号:清研学堂,在这里你可以学到夜白(作者笔名)精心整理的笔记,让我们每天进步一点点,让优秀成为一种习惯! 一.Sqoop的简介: Sqoop是一个数据采集引擎/数据交换引擎,采集关系型数据库(RDBMS)中的数据,主要用于在RDBMS与HDFS/Hive/HBase之间进行数据传递,可以通过sqoop import命令将RDBMS中的数据导入到HDFS/Hive/HBase中,也可以通过sqoop export命令将HDFS/Hive/HBase中的

oracle数据库中提供的5种约束

约束作用:用来保持数据的完整性,防止无效数据进入到数据库中.oracle数据库中提供的5种约束,都是限定某个列或者列的组合的.1.主键约束(PRIMARY KEY):在一个表中能唯一的标识一行.主键可以限定在多个列上.2.唯一键约束(UNIQUE key):在一个表中能唯一的标识一行,唯一键也可以限定在多个列上.主键和唯一键的区别:a.一个表中最多只能有一个主键.可以多个唯一键.b.主键所限定的列不能为null,唯一键所限定的列可以为null.3.外键约束(FOREIGN key):   引用表

oracle数据库中exp/imp之初次使用

最近使用oracle9i时,遇到了数据库备份和恢复的问题,很多时候与exp和imp的命令使用有关.针对遇到的问题,简单的总结. 问题的背景,就是需要对某张表导入和导出.数据库database1,dateabase2在数据库中涉及的用户主要是 user1,user2,备份的对象主要是表table1. 对于exp和Imp命令的内容网上较多的解说,这里主要解决具体的问题,使用的只是常用的命令项. 1.从database2中导出表 table1 exp userid=system/[email prot