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就介绍到这里。