C# ORM NHibernate实践

    前阵有个应用程序项目需要连接数据库mysql,想着单纯写sql语句做CRUD太麻烦了,就想起了ORM技术,简单点说就是把数据库的表映射成对象,然后可以通过框架提供的函数进行CRUD,也可以通过写sql语句的方式,最方便的就是能跟对象做映射,输入和输出都很方便,针对C#的程序,选择了NHibernate。

    NHibernate是一个面向.NET环境的对象/关系数据库映射工具。对象/关系数据库映射(object/relational mapping,ORM)这个术语表示一种技术,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。

一、安装配置    

    从官网下载NHibernate的安装包,在C#的项目中将dll通过引用添加到项目中,即可完成NHibernate的安装,重点是配置hibernate.cfg.xml和创建一个NHibernate辅助类。

    下面是我项目中hibernate.cfg.xml的配置(IP:数据库IP,DBName:数据库名,User:用户名,Password:密码,ProjectName:工程名) 和文件属性:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
    <property name="query.substitutions">hqlFunction=SQLFUNC</property>
    <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
    <property name="connection.connection_string">Data Source=IP;Port=3306;Database=DBName;User Id=User;Password=Password;CHARSET=utf8</property>
    <property name="show_sql">false</property>
    <property name="hbm2ddl.keywords">none</property>
    <mapping assembly="ProjectName" />
  </session-factory>
</hibernate-configuration>

    NHibernate辅助类 NHibernateHelper.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using NHibernate.Cfg;
using ProjectName.DB.Domain;
using ProjectName.Util;

namespace ProjectName.DB.Dao
{
    class NHibernateHelper
    {
        private static ISessionFactory _sessionFactory;

        private static ISessionFactory SessionFactory
        {
            get
            {
                if (_sessionFactory == null)
                {
                    var configuration = new Configuration();
                    configuration.Configure("./DB/Mapping/hibernate.cfg.xml");
                    try
                    {
                        //configuration.AddAssembly(typeof(UserInfo).Assembly);    //??
                        _sessionFactory = configuration.BuildSessionFactory();
                    }
                    catch (Exception ex)
                    {
                        MessageBoxUtils.Error("[NHibernateHelper]数据库连接失败,请检查网络和参数,建议稍后重试",ex);
                    }

                }
                return _sessionFactory;
            }
        }

        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }

        public static IStatelessSession OpenStatelessSession()
        {
            return SessionFactory.OpenStatelessSession();
        }

    }
}

    为了管理方便,我把DB模块单独拎出来,还分了不同的目录:

 

Dao主要放数据库的CRUD操作,Domain放数据表对象,Mapping放对象和数据表的映射,Service是服务层。

二、实践

下面以商品为例,介绍实际的操作和一些处理:

CommodityInfo.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Drawing;

namespace MissPatinaERP.DB.Domain
{
    public class CommodityInfo
    {
        [DisplayName("商品编码")]
        public virtual int commodityCode { get; set; }

        [DisplayName("商品名称")]
        public virtual string commodityName { get; set; }

        [DisplayName("颜色")]
        public virtual int colorCode { get; set; }

        [DisplayName("尺码")]
        public virtual int sizeCode { get; set; }

        [DisplayName("款式")]
        public virtual int styleCode { get; set; }

        [DisplayName("吊牌价")]
        public virtual double tagPrice { get; set; }

        [DisplayName("单位")]
        public virtual int unitCode { get; set; }

        [DisplayName("备注")]
        public virtual string remarks { get; set; }

        [DisplayName("创建者")]
        public virtual string createUser { get; set; }

        [DisplayName("创建时间")]
        public virtual DateTime createTime { get; set; }

        [DisplayName("更新时间")]
        public virtual DateTime updateTime { get; set; }

    }

    public class CommodityAttributeConfig
    {
        [DisplayName("配置项类型")]
        public virtual string configType { get; set; }

        [DisplayName("配置项名称")]
        public virtual string typeName { get; set; }

        [DisplayName("属性编码")]
        public virtual int attributeCode { get; set; }

        [DisplayName("英文名称")]
        public virtual string attributeNameEn { get; set; }

        [DisplayName("中文名称")]
        public virtual string attributeNameCn { get; set; }
        //联合主键时必须重载Equals和GetHashCode两个方法
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;
            var t = obj as CommodityAttributeConfig;
            if (t == null)
                return false;
            if (configType == t.configType && attributeCode == t.attributeCode)
                return true;
            return false;
        }
        public override int GetHashCode()
        {
            return (configType + "|" + attributeCode).GetHashCode();
        }
    }
}

    注:建对象时注意成员必须加上virtual关键字,还有如果对应的数据表是联合主键,类必须重载Equals和GetHashCode两个方法;C# 只要在定义成员变量时加上{get; set;},就相当于实现了get和set两个方法,这个很方便。

CommodityMapper.xml

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="ProjectName"
                   namespace="ProjectName.DB.Domain">

  <class name="CommodityInfo" table="commodity_info">
    <id name="commodityCode" column="commodity_code" type="int">
      <generator class="identity"/>
    </id>
    <property name="commodityName" column="commodity_name" type="string" length="50" />
    <property name="colorCode" column="color_code" type="int"/>
    <property name="sizeCode" column="size_code" type="int"/>
    <property name="styleCode" column="style_code" type="int"/>
    <property name="tagPrice" column="tag_price" precision="20" scale="2"/>
    <property name="unitCode" column="unit_code" type="int"/>
    <property name="remarks" column="remarks" type="string" length="100" />
    <property name="createUser" column="create_user" type="string" length="50" />
    <property name="createTime" column="create_time" type="DateTime"/>
    <property name="updateTime" column="update_time" insert="false" update="false" type="DateTime"/>
   </class>

  <class name="CommodityAttributeConfig" table="commodity_attribute_config">
    <composite-id>
      <key-property name="configType" column="config_type" type="string" length="20"></key-property>
      <key-property name="attributeCode" column="attribute_code" type="int"></key-property>
    </composite-id>
    <property name="typeName" column="type_name" type="string" length="50" />
    <property name="attributeNameEn" column="attribute_name_en" type="string" length="50"/>
    <property name="attributeNameCn" column="attribute_name_ch" type="string" length="100" />
  </class>

</hibernate-mapping>

注:对于自增的主键,增加<generator class="identity"/>,就可以在插入的时候不需要赋值,让数据库自增;

      如果想让列自己按照default值去赋值,可以在property后面增加insert="false" update="false";

      对于组合主键,需要用<composite-id>来标识;

      对于mapper.xml文件,还需按照以下属性配置,否则无法识别:

     

CommodityDao.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using System.Windows;
using ProjectName.DB.Domain;
using ProjectName.Util;

namespace ProjectName.DB.Dao
{
    class CommodityDao
    {
        ISession session = NHibernateHelper.OpenSession();
        /// <summary>
        /// 由hql语句查询获得列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="hql"></param>
        /// <returns></returns>
        public CommodityInfo QueryInfo(int commodityCode)
        {
            string hql = "from ProjectName.DB.Domain.CommodityInfo u where u.commodityCode=" + commodityCode;
            IList<CommodityInfo> commodityList = session.CreateQuery(hql).List<CommodityInfo>();
            if (commodityList != null && commodityList.Count > 0)
            {
                return commodityList.First<CommodityInfo>();
            }
            else
            {
                return default(CommodityInfo);
            }
        }

        public IList<CommodityInfo> QueryInfo<CommodityInfo>()
        {
            string hql = "from ProjectName.DB.Domain.CommodityInfo order by createTime desc";
            return session.CreateQuery(hql).List<CommodityInfo>();
        }

        public int Save(CommodityInfo commodity)
        {
            using (ITransaction transaction = session.BeginTransaction())
            {
                var identifier = session.Save(commodity);
                transaction.Commit();
                return Convert.ToInt32(identifier);
            }
        }

        public void Update(CommodityInfo commodity)
        {
            using (ITransaction transaction = session.BeginTransaction())
            {
                CommodityInfo originCommodity = session.Get<CommodityInfo>(commodity.commodityCode);
                session.Evict(originCommodity);
                session.SaveOrUpdate(commodity);
                transaction.Commit();
            }
        }

        public void Delete(CommodityInfo commodity)
        {
            using (ITransaction transaction = session.BeginTransaction())
            {
                CommodityInfo originCommodity = session.Get<CommodityInfo>(commodity.commodityCode);
                session.Evict(originCommodity);
                session.Delete(commodity);
                transaction.Commit();
            }
        }

    }
}

    首先需要创建一个session会话,用该session来对数据库进行操作,NHibernate提供了友好的API对数据库表进行CRUD,既可以用session.CreateQuery(hql)的方式,编写hql(类似于sql)对数据库进行查询,也可以直接用session.Get()方法;

      session.Save()指数据的插入保存,session.Update()指数据的更新,而session.SaveOrUpdate()即提供插入也提供更新,当记录不存在时插入,记录存在时更新,session.Delete()用于删除数据,但是在更新和删除数据时,会产生session的对象冲突,需要将原有对象用session.Evict()掉,再更新和删除才不会出错。

     上面的数据库操作不需要在mapper.xml中增加特别的配置,只要存在数据表的配置就可以了;NHibernate通过session.GetNameQuery("")可以执行复杂的sql'查询和存储过程调用查询,在CommodityMapper.xml中增加如下配置:

  <resultset name="commodityResult">
    <return-scalar column="code" type="int"/>
    <return-scalar column="name" type="string"/>
    <return-scalar column="color" type="string"/>
    <return-scalar column="size" type="string"/>
    <return-scalar column="price" type="Double"/>
  </resultset>

  <sql-query name="GetCommodityDisplay" resultset-ref="commodityResult">
    SELECT
    c.commodity_code AS code,
    c.commodity_name AS name,
    a3.attribute_name_ch AS color,
    a4.attribute_name_ch AS size,
    c.tag_price as price
    FROM commodity_info c
    LEFT OUTER JOIN commodity_attribute_config a3
    ON c.color_code = a3.attribute_code AND a3.config_type = 'color'
    LEFT OUTER JOIN commodity_attribute_config a4
    ON c.size_code = a4.attribute_code AND a4.config_type = 'size'
    LIMIT :size
  </sql-query>

    上面的功能是传入的要查询的记录数,获取产品的信息,由于商品中的颜色和尺码是以配置码的方式存在的,所以需要关联商品配置信息表获取名称,这时候就需要用关联的复杂sql查询,增加上述配置后,需要分别在CommodityInfo.cs和CommodityDao.cs增加类CommodityResult和GetCommidtyDisplay的方法,如下:

   public class CommodityResult
    {
        [DisplayName("商品编码")]
        public virtual int Code { get; set; }

        [DisplayName("颜色")]
        public virtual string Color { get; set; }

        [DisplayName("尺码")]
        public virtual string Size { get; set; }

        [DisplayName("吊牌价")]
        public virtual double Price { get; set; }

        [DisplayName("商品名称")]
        public virtual string Name { get; set; }

        public CommodityResult()
        { }

        public CommodityResult(int code, string name, string color, string size, double price) 
        {
            this.Code = code;
            this.Name = name;
            this.Color = color;
            this.Size = size;
            this.Price = price;
        }     
    }
public IList<CommodityResult> GetCommodityDisplay(int size)
        {
            IList<object[]> query = session.GetNamedQuery("GetCommodityDisplay")
                .SetParameter("size", size)
                .List<object[]>();
            IList<CommodityResult> result = IListObject2CommodityResult(query);
            return result;
        }

private IList<CommodityResult> IListObject2CommodityResult(IList<object[]> query) 
        {
            IList<CommodityResult> result = query.Select(res =>
                new CommodityResult(Convert.ToInt32(res[0]),
                    res[1] == null ? "" : res[1].ToString(),
                    res[2] == null ? "" : res[2].ToString(),
                    res[3] == null ? "" : res[3].ToString(),
                    res[4] == null ? 0 : Convert.ToDouble(res[4])
                    )).ToList();
            return result;
        }

还可以执行存储过程返回结果:

存储过程summary_commodity():

CREATE DEFINER=`user`@`%` PROCEDURE `summary_commodity`(IN `i_color` int)
BEGIN
    select sum(1) FROM commodity_info where color_code = i_color;
END
  <sql-query name="callSummaryCommodity">
    <return-scalar column="quantity" type="int"/>
    call summary_commodity(:color)
  </sql-query>
public int CallCommoditySummary(int colorCode)
        {
            IList<int> query = session.GetNamedQuery("callSummaryCommodity")
                .SetParameter("color", colorCode)
                .List<int>();

            int result = query.Select(res => res).Sum();
            return result;
        }

    对于Servie层,只要调用Dao的方法就可以了,这里就不列出代码,但是我实现一种通过的Service接口,可以适应单一主键的数据表的增删改查,如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ProjectName.DB.Service
{
    public interface IBasService<T> where T : class
    {
        IList<T> GetAll();
        IQueryable<T> Query();
        T GetById(int id);
        T GetByKey(string key);
        T LoadById(int id);
        int Save(T obj);
        void UpdateById(T obj, int id);
        void UpdateByKey(T obj, string key);
        void DeleteById(int id);
        void DeleteByKey(string key);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate;
using NHibernate.Linq;
using ProjectName.DB.Dao;


namespace ProjectName.DB.Service
{
    public class BasService<T> : IBasService<T> where T : class
    {
        ISession Session = NHibernateHelper.OpenSession();

        public IList<T> GetAll()
        {
            IList<T> list = Session.CreateCriteria<T>().List<T>();
            return list;
        }

        public virtual IQueryable<T> Query()
        {
            var result = Session.Query<T>();
            return result;
        }

        public T GetById(int id)
        {
            T obj = Session.Get<T>(id);
            return obj;
        }

        public T GetByKey(string key)
        {
            T obj = Session.Get<T>(key);
            return obj;
        }

        public T LoadById(int id)
        {
            T obj = Session.Load<T>(id);
            return obj;
        }

        public int Save(T obj)
        {
            var identifier = Session.Save(obj);
            
            return Convert.ToInt32(identifier);
        }

        public void UpdateById(T obj, int id )
        {
            using (ITransaction transaction = Session.BeginTransaction())
            {
                T originObj = GetById(id);
                Session.Evict(originObj);
                Session.SaveOrUpdate(obj);
                transaction.Commit();
            }
        }

        public void UpdateByKey(T obj, string key)
        {
            using (ITransaction transaction = Session.BeginTransaction())
            {
                T originObj = GetByKey(key);
                Session.Evict(originObj);
                Session.SaveOrUpdate(obj);
                transaction.Commit();
            }
        }

        public void DeleteById(int id)
        {
            using (ITransaction transaction = Session.BeginTransaction())
            {
                T obj = GetById(id);
                Session.Evict(obj);
                Session.Delete(obj);
                transaction.Commit();
            }
            
        }

        public void DeleteByKey(string key)
        {
            using (ITransaction transaction = Session.BeginTransaction())
            {
                T obj = GetByKey(key);
                Session.Evict(obj);
                Session.Delete(obj);
                transaction.Commit();
            }

        }
    }
}

使用的时候,只要定义一个实例,用实际的类名替换BasService<T>中的T就行,比如:

private BasService<CommodityInfo> CommodityService = new BasService<CommodityInfo>();
然后那你就可以根据的key是整型还是字符型,选择对应的ById还是ByKey,是不是很方便,好了C#的ORM NHibernate就介绍到这里。



猜你喜欢

转载自blog.csdn.net/kxiaozhuk/article/details/80190341
Orm