NHibernate使用介绍
1.总体介绍
1.1 简介
NHibernate 是一个基于.Net 的针对关系型数据库的对象持久化类库。NHibernate 来源于非常优秀的基于Java的Hibernate 关系型数据库持久化工具。NHibernate 从数据库底层来持久化你的.Net 对象到关系型数据库。使用NHibernate,可以让应用层程序不直接操作数据库,而是操作NHibernate提供的类库,使得应用程序对数据库的可移植性大大增强了。
1.2 NHibernate的体系结构
NHibernate的整体体系结构如下图所示:
图一 NHibernate的整体体系结构图
从上图可以看出,NHibernate处于Application和Database之间,把这两个模块分离,从而使得应用程序的可移植性得以实现。
NHibernate系统结构简图如下图所示:
图二 NHibernate系统结构简图
从上图中可以看出,在应用程序(Application)中创建的临时对象(Transient objects),开始是保存在内存中的;接着应用程序和NHibernate进行对话(Session,事先需要先由SessionFactory创建Session),告知NHibernate需要进行的持久化请求;NHibernate根据配置文件(XML Mapping和app.config)把持久化操作转化为相应的数据库操作,调用相应的ADO.Net API执行数据库操作。
1.3 NHibernate支持的数据库
NHibernate最初是在Microsoft SQL Server 2000上测试的,现在已经支持的数据库有:
l Microsoft SQL Server 2000
l Oracle
l Microsoft Access
l Firebird
l PostgreSQL
l DB2 UDB
l MySQL
l SQLite
1.3 NHibernage的主要类的说明
l NHibernate.Cfg.Configuration
Configuration对象能够搜索以hbm.xml结尾的配置文件,负责解析所有.Net对象和后台数据库中表之间的映射关系;同时它也负责ISessionFactory的建立。
l NHibernate.ISessionFactory
通过调用Configuration对象的BuildSessionFactory方法可以得到一个ISessionFactory的对象;它是ISession的工厂,应用程序中可以通过它获得一个ISession的对象;
l NHibernate.ISession
ISession代表了应用程序与数据库之间的一次会话,它封装了到后台数据库的一个ADO.NET连接;同时,它也是ITransaction的工厂,保存着数据库中数据的缓存,提供了操作这些数据的方法。
l NHibernate.ITransaction
事务代表了一批工作的原子操作,NHibernate的事务处理是通过ITransaction来实现的,它也是底层ADO.NET的事务的一个抽象,可以调用ISession的BeginTransaction方法得到一个ITransaction的对象,因此,一个Session可以跨越多个Transaction事务。
2.NHibernate的基本使用方法
NHibernate的使用,需要按照以下步骤来进行:
l 每个数据库表对应一个持久化类的定义;
l 每个数据库表对应一个.hbm.xml文件;
l 编写一个连接数据库的配置文件app.config(如果是web项目,则为web.config);
l 使用NHibernate提供的类来实现数据的存储。
使用CodeSmith等第三方工具,可以连接到数据库,为每个表分别生成.hbm.xml配置文件和持久化类的定义。下面,将分别对这些步骤进行详细的介绍。
2.1 一个简单的例子
选取SQL Server2000数据库做例子。
第一步,创建一张表ProbaServer
CREATE TABLE ProbaServer (
ServID int IDENTITY,
ServStartTime datetime NULL,
ServIP nvarchar(15) NULL,
ServName nvarchar(10) NULL,
ServFun nvarchar(10) NULL,
ServPlace text NULL,
)
go
ALTER TABLE ProbaServer
ADD PRIMARY KEY (ServID ASC)
go
第二步,写一个与ProbaServer表对应的.Net持久化类ProbaServer.cs
using System;
using System.Collections;
namespace NHTest
{
#region ProbaServer
/// <summary>
/// ProbaServer object for NHibernate mapped table 'ProbaServer'.
/// </summary>
public class ProbaServer
{
#region Member Variables
protected int _id;
protected DateTime _servStartTime;
protected string _servIP;
protected string _servName;
protected string _servFun;
protected string _servPlace;
#endregion
#region Constructors
public ProbaServer() { }
public ProbaServer( DateTime servStartTime, string servIP, string servName, string servFun, string servPlace )
{
this._servStartTime = servStartTime;
this._servIP = servIP;
this._servName = servName;
this._servFun = servFun;
this._servPlace = servPlace;
}
#endregion
#region Public Properties
public int Id
{
get {return _id;}
set {_id = value;}
}
public DateTime ServStartTime
{
get { return _servStartTime; }
set { _servStartTime = value; }
}
public string ServIP
{
get { return _servIP; }
set
{
if ( value != null && value.Length > 15)
throw new ArgumentOutOfRangeException("Invalid value for ServIP", value, value.ToString());
_servIP = value;
}
}
public string ServName
{
get { return _servName; }
set
{
if ( value != null && value.Length > 10)
throw new ArgumentOutOfRangeException("Invalid value for ServName", value, value.ToString());
_servName = value;
}
}
public string ServFun
{
get { return _servFun; }
set
{
if ( value != null && value.Length > 10)
throw new ArgumentOutOfRangeException("Invalid value for ServFun", value, value.ToString());
_servFun = value;
}
}
public string ServPlace
{
get { return _servPlace; }
set
{
_servPlace = value;
}
}
#endregion
}
#endregion
}
第三步,写一个与ProbaServer表对应的映射文件ProbaServer.hbm.xml。映射文件的作用是建立持久化类与数据库之间的关系,让NHibernate知道如何从一个类的字段映射到数据库中表的字段。
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="NHTest.ProbaServer, NHTest" table="ProbaServer">
<id name="Id" type="Int32" unsaved-value="null">
<column name="ServID" length="4" sql-type="int" not-null="true" unique="true" index="PK__ProbaServer__628FA481"/>
<generator class="native" />
</id>
<property name="ServStartTime" type="DateTime">
<column name="ServStartTime" length="8" sql-type="datetime" not-null="false"/>
</property>
<property name="ServIP" type="String">
<column name="ServIP" length="15" sql-type="nvarchar" not-null="false"/>
</property>
<property name="ServName" type="String">
<column name="ServName" length="10" sql-type="nvarchar" not-null="false"/>
</property>
<property name="ServFun" type="String">
<column name="ServFun" length="10" sql-type="nvarchar" not-null="false"/>
</property>
<property name="ServPlace" type="String">
<column name="ServPlace" length="16" sql-type="text" not-null="false"/>
</property>
</class>
</hibernate-mapping>
注意:需要把.hbm.xml文件的[生成操作]属性设置为[嵌入的资源]。
第四步,编写一个连接数据库的配置文件App.config。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b 77a 5c 561934e089" />
</configSections>
<nhibernate>
<!--连接数据提供者 -->
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />
<!--连接数据方言最常用的是MsSql2000Dialect -->
<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" />
<!--连接数据驱动类-->
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" />
<!--连接数据库-->
<add key="hibernate.connection.connection_string" value="workstation id=192.168.1.200;initial catalog=test;user id=sa;pwd=sa" />
</nhibernate>
</configuration>
同时,在工程中引用对应的dll文件NHibernate.dll
第五步,NHibernate提供的类来实现数据的CRUD操作。
添加记录到数据库
void AddServer()
{
Configuration config = null;
ISessionFactory sessionFactory = null;
try
{
// 实例化Configuration对象
config = new Configuration();
// 加载本程序集里所有.hbm.xml配置文件
config.AddAssembly("NHTest");
// 获得ISessionFactory
sessionFactory = config.BuildSessionFactory();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 开始一个事务
ITransaction transaction = session.BeginTransaction();
// 创建一个服务器对象,此时为非持久化对象
ProbaServer server = new ProbaServer();
server.ServStartTime = DateTime.Now;
server.ServIP = "192.168.1.200";
server.ServName = "算法服务器";
server.ServFun = "提供算法";
server.ServPlace = "二楼机房";
try
{
// 将服务器对象保存到数据库,持久化对象
session.Save(server);
// 提交事务
transaction.Commit();
Console.WriteLine("保存成功");
}
catch (Exception ex)
{
// 回滚事务
transaction.Rollback();
Console.WriteLine(ex.Message);
}
finally
{
// 关闭会话
session.Close();
}
}
查询所有服务器
void ReadServer()
{
Configuration config = null;
ISessionFactory sessionFactory = null;
try
{
// 实例化Configuration对象
config = new Configuration();
// 加载本程序集里所有.hbm.xml配置文件
config.AddAssembly("NHTest");
// 获得ISessionFactory
sessionFactory = config.BuildSessionFactory();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
// 开始一个会话
ISession session = sessionFactory.OpenSession();
try
{
// 取得所有的服务器
IList servers = session.CreateCriteria(typeof(ProbaServer)).List();
foreach (ProbaServer server in servers)
{
Console.WriteLine(server.Id.ToString() + ":" + server.ServName);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
// 关闭会话
session.Close();
}
}
更新服务器
void UpdateServer()
{
Configuration config = null;
ISessionFactory sessionFactory = null;
try
{
// 实例化Configuration对象
config = new Configuration();
// 加载本程序集里所有.hbm.xml配置文件
config.AddAssembly("NHTest");
// 获得ISessionFactory
sessionFactory = config.BuildSessionFactory();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 开始一个事务
ITransaction transaction = session.BeginTransaction();
// 创建一个服务器对象
ProbaServer server = new ProbaServer();
server.Id = 1;
server.ServStartTime = DateTime.Now;
server.ServIP = "192.168.1.201";
server.ServName = "更新算法服务器";
try
{
// 将服务器对象保存到数据库,持久化对象
session.Update(server);
// 提交事务
transaction.Commit();
Console.WriteLine("更新成功");
}
catch (Exception ex)
{
// 回滚事务
transaction.Rollback();
Console.WriteLine(ex.Message);
}
finally
{
// 关闭会话
session.Close();
}
}
删除服务器
void DeleteServer()
{
Configuration config = null;
ISessionFactory sessionFactory = null;
try
{
// 实例化Configuration对象
config = new Configuration();
// 加载本程序集里所有.hbm.xml配置文件
config.AddAssembly("NHTest");
// 获得ISessionFactory
sessionFactory = config.BuildSessionFactory();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw ex;
}
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 开始一个事务
ITransaction transaction = session.BeginTransaction();
// 创建一个服务器对象
ProbaServer server = new ProbaServer();
server.Id = 2;
try
{
// 将服务器对象保存到数据库,持久化对象
session.Delete(server);
// 提交事务
transaction.Commit();
Console.WriteLine("删除成功");
}
catch (Exception ex)
{
// 回滚事务
transaction.Rollback();
Console.WriteLine(ex.Message);
}
finally
{
// 关闭会话
session.Close();
}
}
2.2 持久化类
持久化类是是在应用程序中定义的,用来解决商业问题的类,它与关系型数据库的表一一对应。编写持久化类主要有以下规则:
l 为持久化类声明访问器getters和setters;
l 为持久化类实现一个默认的构造函数,这是必须的,NHibernate需要通过默认的构造函数来实例化这些持久化类;
l 提供一个标识属性(可选)
l 建议使用不是sealed的类(可选)
2.3 映射文件hbm.xml
程序中的对象与数据库的表之间的映射是通过一个XML文档来定义的,这个映射文档被设计为可读的,并且可以手动修改。映射语言是以对象为中心的,那就意味着映射是按照持久化类的定义来创建的,而不是表的定义。
虽然映射文件可以通过手动创建,但是也有一些工具能够通过数据库的表的定义来自动生成映射文件,比如CodeSmith和MyGeneration。
本文档的1.2节包含有一个简单的映射文件,下面就以这个文件为例介绍映射文件各个元素和属性的含义和取值。
1. hibernate-mapping
这个元素包含以下几个主要的属性:
<hibernate-mapping
schema="schemaName" (1)
default-cascade="none|save-update" (2)
auto-import="true|false" (3)
assembly="Eg" (4)
namespace="Eg" (5)
/>
(1) schema (可选): 数据库的schema名称;
(2) default-cascade (可选 - 默认为none): 默认的级联风格;
(3) auto-import(可选 - 默认为 true): 指定是否我们可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类);
(4) assembly (可选): 指定一个程序集,如果在映射文档中没有指定程序集,就使用这个程序集;
(5) namespace (可选): 指定一个命名空间前缀,如果在映射文档中没有指定全限定名,就使用这个命名空间名。
注意,假若你有两个持久化类,它们的非全限定名是一样的,就是在不同的命名空间里面,应该设置auto-import="false"。
2. class
class元素用来定义一个持久化类,一个持久化类对应一个class元素。
<class
name="ClassName" (1)
table="tableName" (2)
discriminator-value="discriminator_value" (3)
mutable="true|false" (4)
schema="owner" (5)
proxy="ProxyInterface" (6)
dynamic-update="true|false" (7)
dynamic-insert="true|false" (8)
polymorphism="implicit|explicit" (9)
where="arbitrary sql where condition" (10)
persister="PersisterClass" (11)
lazy="true|false" (12)
/>
(1) name: 持久化类(或者接口)的全限定名,包含其程序集和命名空间;
(2) table: 对应的数据库表名。
(3) discriminator-value (可选 - 默认和类名一样): 一个用于区分不同的子类的值,在多态行为时使用。
(4) mutable (可选, 默认为 true): 表明该类的实例可变(不可变)。
(5) schema (可选): 覆盖在根<hibernate-mapping> 元素中指定的schema名字。
(6) proxy (可选): 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。
(7) dynamic-update (可选, 默认为 false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。
(8) dynamic-insert (可选, 默认为 false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。
(9) polymorphism (可选, 默认为 implicit(隐式)): 界定是隐式还是显式的使用查询多态。
(10)where (可选) 指定一个附加的SQL WHERE 条件,在抓取这个类的对象时会一直增加这个条件。
(11)persister (可选): 指定一个定制的 IClassPersister.
(12)lazy(可选):假若设置 lazy="true",就是设置这个类自己的名字作为proxy接口的一种等价快捷形式。
3. id
被映射的类必须声明对应数据库表主键字段。大多数类有一个属性,为每一个实例包含唯一的标识。 <id> 元素定义了该属性到数据库表主键字段的映射。
<id
name="PropertyName" (1)
type="typename" (2)
column="column_name" (3)
unsaved-value="any|none|null|id_value" (4)
access="field|property|nosetter|ClassName(5)">
<generator class="generatorClass"/>
</id>
(1) name (可选): 标识属性的名字。
(2) type (可选): 标识NHibernate类型的名字。
(3) column (可选 - 默认为属性名): 主键字段的名字。
(4) unsaved-value (可选 - 默认为 null): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注)但未再次持久化的实例区分开来。
(5) access (可选 - 默认为 property): NHibernate用来访问属性值的策略。
另外,还有一个<composite-id>元素用于声明映射到多主键的表,但是NHibernate提供的文档强烈建议不要使用这种方式,解决的方法是为对主键的表另外添加一个标识字段来作为主键,映射则用<id>元素的完成。
id元素有一个子元素<generator>是必须声明的,它指定一个类来为这个持久化类的实例生成一个唯一的标识。所有的生成器都实现NHibernate.Id.IdentifierGenerator接口。这是一个非常简单的接口;某些应用程序可以选择提供他们自己特定的实现。当然,NHibernate提供了很多内置的实现。一般设定为native,NHibernate会根据底层数据库的能力自动选择一种生成器。
4. property
property元素为类声明了一个属性,一个类的属性对应一个property元素。
<property
name="propertyName" (1)
column="column_name" (2)
type="typename" (3)
update="true|false" (4)
insert="true|false" (5)
formula="arbitrary SQL expression" (6)
access="field|property|ClassName" (7)
/>
(1) name:属性的名字
(2) column (可选 - 默认为属性名字): 对应的数据库字段名。
(3) type (可选): 一个NHibernate类型的名字。
(4) update (可选 - 默认为 true) : 表明在用于UPDATE 的SQL语句中是否包含这个字段。
(5) insert (可选 - 默认为 true) : 表明在用于INSERT的SQL语句中是否包含这个字段。这二者如果都设置为false则表明这是一个“外源性(derived)”的属性,它的值来源于映射到同一个(或多个)字段的某些其它属性,或者通过一个trigger(触发器),或者其它程序。
(6) formula (可选): 一个SQL表达式,定义了这个计算(computed) 属性的值。计算属性没有和它对应的数据库字段。
(7) access (可选 - 默认为 property): NHibernate用来访问属性值的策略。
2.4 配置信息
NHibernate可以在多种数据库平台下工作,这样就需要提供一个和数据库相关的配置信息,这些信息是通过NHibernate.Cfg.Configuration对象来实现配置的。
2.4.1 NHibernate支持的配置参数
NHibernate提供了许多配置参数来控制NHibernate的运行方式,除了极少数如数据库连接字符串等是必须的外,绝大多少都有默认的参数值。下表列出了部分NHibernate提供的配置信息及其用途。
hibernate.connection.provider | 指定IConnectionProvider的类型,是必须的 |
hibernate.connection.driver_class | 指定IDriver的类型 |
hibernate.connection.connection_string | 指定连接字符串,是必须的 |
hibernate.connection.isolation | 设置ADO.NET事务的隔离级别,检查System.Data.IsolationLevel以确定取值的具体意义,但是需要查看数据库的文档确保级别是被支持的。 |
| 指定NHibernate的方言类名,方言可以让NHibernate使用具体数据库的特性 |
hibernate.default_schema | 指定数据库默认的schema |
hibernate.use_outer_join | 指定是否允许outer-join的方式查询 |
hibernate.cache.provider_class | 指定一个自定义的ICacheProvider类名 |
hibernate.cache.use_minimal_puts | 增加读的次数,减少写的次数,来优化二级缓存,true/false |
hibernate.cache.use_query_cache | 指定查询结果缓存是否有效,true/false |
hibernate.query.substitutions | 把NHibernate查询的一些短语替换为SQL短语 |
hibernate.show_sql | 是否在日志中显示NHibernate生成的SQL ,true/false |
表一 NHibernate支持的配置参数
2.4.2 配置参数的方法
NHibernate支持多种方式来配置Configuration对象,可以把配置信息写在App.config(如果是web应用程序则为web.config)中,也可以在创建Configuration对象时,在代码中配置参数。推荐在文件中保存配置信息,当数据库配置信息发生改变时,我们只需要修改配置文件即可,不需要修改代码再重新编译。
在文件中写配置信息的方法在2.1节(一个简单的例子)中已经介绍过了,这里介绍一下在程序中设置配置参数的方法。
Configuration config = new Configuration();
config.Properties["hibernate.connection.provider"] = "NHibernate.Connection.DriverConnectionProvider";
config.Properties["hibernate.dialect"] = "NHibernate.Dialect.MsSql2000Dialect";
config.Properties["hibernate.connection.driver_class"] = "NHibernate.Driver.SqlClientDriver";
config.Properties["hibernate.connection.connection_string"] = "workstation id=192.168.1.200;initial catalog=test;user id=sa;pwd=sa";
在代码中给Configuration对象赋值的方式,我们可以自定义的方式去组织和读取配置信息,让NHibernate的配置信息的组织方式与我们程序的其它模块部分的配置信息的组织方式保持一致。
2.5 操作数据
在配置信息,持久化类和映射文件都准备好后,就可以利用这些配置好的资源来按照我们程序的要求操作数据了。操作数据库的一般操作可以划分为增,删,查,改4种,即我们通常说的CRUD,下面分别进行介绍。
2.5.1 建立连接
和其它方式操作数据库一样,NHibernate在访问数据前要先建立连接。与连接相关的配置信息已经在2.4节App.config中下好,现在就可以在程序中写连接的代码了。按照以下步骤来进行:
(1) 初始化Configuration
Configuration config = new Configuration();
(2) 配置Configuration对象
有下面几种方法来配置Configuration对象:
l 通过Configuration.AddAssembly(assemblyName),NHibernate将自动查找所指定程序集中的所有*.hbm.xml嵌入文件获取o/r mapping信息,例如:
config.AddAssembly("NHTest");
l 通过Configuration.AddType(Type t),NHibernate将自动查找当前程序集中名为typeName.hbm.xml的嵌入文件该类的o/r mapping信息,例如:config.AddClass(typeof(ProbaServer));
config.AddClass(typeof(ProbaGateway));.
l 通过Configuration.AddXmlFile(fileName),nh将自动查找当前程序集中名为fileName的嵌入文件,获取o/r mapping信息,不过使用这种方法需要将这些*.hbm.xml文件复制到可执行文件所在的目录。例如:
config.AddXmlFile("ProbaServer.hbm.xml");
config.AddXmlFile("ProbaGateway.hbm.xml");
(3) 获取ISessionFactory对象
ISessionFactory sessionFactory = config.BuildSessionFactory();
1.3节已经介绍过,ISessionFactory是ISession的工厂,现在就可以开始会话了。
(4) 开始一个会话
ISession session = sessionFactory.OpenSession();
现在,就可以在这个会话中调用NHibernate.ISession的方法操作数据了。
2.5.2 添加
用持久化类的对象可以分为持久化和非持久化两种,新创建的一个对象是非持久化的,可以通过NHibernate.ISessio的save方法把它持久化,即将它添加到数据库。
ProbaServer server = new ProbaServer();
server.ServStartTime = DateTime.Now;
server.ServIP = "192.168.1.200";
server.ServName = "算法服务器";
server.ServFun = "提供算法";
server.ServPlace = "二楼机房";
session.Save(server);
2.5.3 查找
从数据库中查找记录是所有操作中使用频率最高的一种,也是最复杂的一种,NHibernate提供的多种灵活的方法来完成查找操作,在本节将介绍几种查询记录的方法。
(1) Load
NHibernate.ISession的Load方法提供了一种通过标识来持久化对象的途径,例如:
ProbaServer server = (ProbaServer)session.Load(typeof(ProbaServer), 12);
Load方法的第二个参数是ProbaServer的标识,即ProbaServer表的主键。
注意,如果指定的标识在数据库中并没有找到对应的记录,Load方法将会抛出一个异常,如果不确定该标识所对应的记录是否存在,可以调用Get方法。
(2) Get
调用Get方法,它会立即查询数据库,如果指定的标识没有对应的记录,它不会抛出异常,而是返回null。使用的例子:
ProbaServer server = (ProbaServer)session.Load(typeof(ProbaServer), 12);
(3) Find
如果不知道要查询的记录的标识,可以调用NHibernate.ISession的Find方法,它提供了条件查询。例如,要从ProbaServer表中检索标识号大于13且IP为192.168.1.200的记录,可以这么写:
IList servers = session.Find("from ProbaServer where ServID > 13 and ServIP = '192.168.1.200'");
这条语句的Find方法的返回值是一个ProbaServer对象的列表(IList)。
如果希望查询的条件参数不固定,可以通过参数给Find方法,调用Find的三个参数的版本,例如:
using NHibernate.Type;
int id = 13;
string ip = "192.168.1.200";
IList servers = session.Find("from ProbaServer where ServID > ? and ServIP = ?",
new object[] { id, ip }, new IType[] { NHibernateUtil.Int32, NHibernateUtil.String });
此方法的第二个参数替换第一个参数中的问号(?),第三个参数指定第二个参数的类型。
前面的几种查询的查询内容都是整个表的所有字段,这个时候Find方法的返回值是持久化类的对象;如果只想查询部分字段,或是使用集函数如MAX,MIN等,这个时候返回值是object[]的数组,例如:
IList multi = session.Find("select T1.ServFun, min(T1.Id), max(T1.ServIP) from ProbaServer as T1 group by T1.ServFun");
foreach (object[] obj in multi)
{
Console.WriteLine(obj[0].ToString() + ":" + obj[1].ToString() + ":" + obj[2].ToString());
}
(4) CreateQuery
CreateQuery仅一个参数,与只有一个参数的Find方法的用法相同,但是它返回的是一个IQuery的实例。如果要对查询的结果集作限定,可以使用ISession的CreateQuery方法,然后调用IQuery的相关方法,可以对结果集进行限定。例如:
IQuery query = session.CreateQuery("from ProbaServer");
query.SetFirstResult(3);
query.SetMaxResults(1);
IList serverList = query.List();
foreach (ProbaServer entity in serverList)
{
Console.WriteLine(entity.Id);
}
SetFirstResult是设置第一个返回对象的位置,可以用于分页;
SetMaxResults是设置最大的结果数,可以用于分页;
List是执行查询,返回满足条件的对象集合。
这个例子中,对结果集进行过滤,只返回结果集的第4条记录(SetFirstResult从0开始计数)。
(5) CreateCriteria
此方法提供了完全面向对象的查询方法,不用再写HQL语句了。例如:
ICriteria crit = session.CreateCriteria(typeof(ProbaServer));
crit.Add(Expression.Gt("Id", 12));
crit.Add(Expression.Eq("ServIP", "192.168.1.200"));
crit.AddOrder(Order.Desc("Id"));
crit.SetFirstResult(1);
crit.SetMaxResults(1);
IList serverList = crit.List();
foreach (ProbaServer entity in serverList)
{
Console.WriteLine(entity.Id);
}
Add是加入条件表达式(Expression对象),调用此方法可以组合多个条件;
AddOrder是加入排序的字段(Order对象),相当于SQL中的Group By。
从上面的例子可以看出,CreateCriteria是通过把HQL的查询条件拆开,使用ICriteria对象的方法来完成查询条件,排序等操作。
2.5.4 更新
调用ISession的Update方法可以删除数据库中的记录。
ITransaction trans = session.BeginTransaction();
ProbaServer server = (ProbaServer)session.Load(typeof(ProbaServer), 15);
server.ServName = "更新后名字";
session.Update(server);
trans.Commit();
2.5.5 删除
调用ISession的Delete方法可以删除数据库中的记录。例如:
ITransaction trans = session.BeginTransaction();
ProbaServer server = (ProbaServer)session.Load(typeof(ProbaServer), 14);
session.Delete(server);
trans.Commit();
上面的方法一次只能删除一条记录,如果想删除多条记录,可以使用Delete的重载方法,与查询中Find方法的参数的使用方法相同。例如:
ITransaction trans = session.BeginTransaction();
int id = 16;
string ip = "192.168.1.200";
session.Delete("from ProbaServer where ServID < ? and ServIP = ?",
new Object[] { id, ip }, new IType[]{NHibernateUtil.Int32, NHibernateUtil.String});
trans.Commit();
2.6 事务处理
对数据库的操作是少不了事务处理的,事务能保证数据完整性和有效性。在NHibernate中,使用ITransaction对象来完成事务的处理。
在NHibernate中,一个典型的事务处理是这样做的:
// 开始一个事务
ITransaction transaction = session.BeginTransaction();
try
{
// 调用session的方法,执行数据操作
...
// 提交事务
transaction.Commit();
}
catch (Exception ex)
{
// 回滚事务
transaction.Rollback();
}
finally
{
// 关闭会话
session.Close();
}
从上面代码可以看出,事务是由ISession创建,创建后事务的处理开始,如果数据库操作成功,就调用Commit方法提交事务,否则调用Rollback方法回滚事务。
事务(ITranscation)与会话(ISession)之间是多对一的关系,也就是说,一个会话中可一个包含多个事务,但是一个事务只能属于一个会话,ISession代表的是数据库与应用程序间的一次交互,而事务将这个交互分成了许许多多的原子操作。看下面的这个例子:
ISession session = sessionFactory.OpenSession();
ITransaction trans = null;
ProbaServer server = null;
try
{
trans = session.BeginTransaction();
server = new ProbaServer();
server.ServStartTime = DateTime.Now;
server.ServIP = "192.168.1.200";
server.ServName = "算法服务器";
server.ServFun = "提供算法";
server.ServPlace = "二楼机房";
session.Save(server);
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
}
session.Disconnect();
// 其它操作
session.Reconnect();
try
{
trans = session.BeginTransaction();
server.ServStartTime = DateTime.Now;
server.ServIP = "192.168.1.100";
server.ServName = "修改的名字";
session.Update(server);
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
}
finally
{
session.Close();
}
注意,如果事务没有处理完,就关闭会话,将会得到不可预知的结果。事务到底是执行提交还是执行回滚,依赖于具体的数据库实现,因此在进行事务处理时要特别注意这一点。测试发现,MS SQL Server 2000会执行回滚。
2.7 one-to-many和many-to-one映射
一对多是数据库中非常常见的关系之一, NHibernate是通过one-to-many和many-to-one元素来实现这种关系的。下面用一个例子介绍这种映射关系,以及如何利用这种关系来实现级联操作。
本节将通过ProbaServer和ProbaGateway这对父子关联表来描述一对多的映射关系,表的关系图如下图所示:
图三 Parent表与Child表关系图
创建ProbaServer表和ProbaGateway表的SQL语句:
CREATE TABLE ProbaGateway (
GwID int IDENTITY,
GwStartTime datetime NULL,
GwName nvarchar(10) NULL,
GwIP nvarchar(15) NULL,
ServID int NOT NULL,
GwPlace text NULL,
GwFun nvarchar(10) NULL,
)
go
ALTER TABLE ProbaGateway
ADD PRIMARY KEY (GwID ASC)
go
CREATE TABLE ProbaServer (
ServID int IDENTITY,
ServStartTime datetime NULL,
ServIP nvarchar(15) NULL,
ServName nvarchar(10) NULL,
ServFun nvarchar(10) NULL,
ServPlace text NULL,
)
go
ALTER TABLE ProbaServer
ADD PRIMARY KEY (ServID ASC)
go
ALTER TABLE ProbaGateway
ADD FOREIGN KEY (ServID)
REFERENCES ProbaServer (ServID)
ON DELETE NO ACTION
ON UPDATE NO ACTION
go
持久化类定义(部分):
// ProbaServer.cs
using System;
using System.Collections;
namespace NHTest
{
#region ProbaServer
/// <summary>
/// ProbaServer object for NHibernate mapped table 'ProbaServer'.
/// </summary>
public class ProbaServer
{
#region Member Variables
protected int _id;
protected IList _probaGateways;
#endregion
#region Constructors
public ProbaServer() { }
public ProbaServer(DateTime servStartTime, string servIP, string servName, string servFun, string servPlace)
{
this._servStartTime = servStartTime;
this._servIP = servIP;
this._servName = servName;
this._servFun = servFun;
this._servPlace = servPlace;
}
#endregion
#region Public Properties
public int Id
{
get { return _id; }
set { _id = value; }
}
public IList ProbaGateways
{
get
{
if (_probaGateways == null)
{
_probaGateways = new ArrayList();
}
return _probaGateways;
}
set { _probaGateways = value; }
}
#endregion
}
#endregion
}
// ProbaGateway.cs
using System;
using System.Collections;
namespace NHTest
{
#region ProbaGateway
/// <summary>
/// ProbaGateway object for NHibernate mapped table 'ProbaGateway'.
/// </summary>
public class ProbaGateway
{
#region Member Variables
protected int _id;
protected ProbaServer _probaServer;
#endregion
#region Constructors
public ProbaGateway() { }
public ProbaGateway(DateTime gwStartTime, string gwName, string gwIP, string gwPlace, string gwFun, ProbaServer probaServer)
{
this._gwStartTime = gwStartTime;
this._gwName = gwName;
this._gwIP = gwIP;
this._gwPlace = gwPlace;
this._gwFun = gwFun;
this._probaServer = probaServer;
}
#endregion
#region Public Properties
public int Id
{
get { return _id; }
set { _id = value; }
}
public ProbaServer ProbaServer
{
get { return _probaServer; }
set { _probaServer = value; }
}
#endregion
}
#endregion
}
映射文件(部分)定义如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="NHTest.ProbaServer, NHTest" table="ProbaServer">
<id name="Id" type="Int32" unsaved-value="null">
<column name="ServID" length="4" sql-type="int" not-null="true" unique="true" index="PK__ProbaServer__06CD 04F 7"/>
<generator class="native" />
</id>
<bag name="ProbaGateways" inverse="true" lazy="false" cascade="all-delete-orphan">
<key column="ServID"/>
<one-to-many class="NHTest.ProbaGateway, NHTest"/>
</bag>
</class>
<class name="NHTest.ProbaGateway, NHTest" table="ProbaGateway">
<id name="Id" type="Int32" unsaved-value="null">
<column name="GwID" length="4" sql-type="int" not-null="true" unique="true" index="PK__ProbaGateway__04E4BC85"/>
<generator class="native" />
</id>
<many-to-one name="ProbaServer" class="NHTest.ProbaServer, NHTest">
<column name="ServID" length="4" sql-type="int" not-null="true"/>
</many-to-one>
</class>
</hibernate-mapping>
映射文件的<class>,<id>等元素在2.3节已经介绍过,这里只介绍<bag>,<key>, <one-to-many>, <many-to-one>等元素。
l bag元素
用于定义System.Collection.IList的类型的集合,重要的属性:
属性 | 用途 | 例子 |
name | 指示映射的属性名称,必须 | ProbaGateways |
lazy | 指示是否使用延迟加载,可选 | true | false |
cascade | 指示级联操作类型,可选 | all-delete-orphan |
关于级联操作类型的类型说明如下:
取值 | 用途 |
none | 默认值,不进行级联操作 |
save-update | 进行级联的save和update操作 |
delete | 进行级联的delete操作 |
all | 进行级联的save, update, delete操作 |
delete-orphan | 删除无相关父对象的子对象 |
all-delete-orphan | all + delete-orphan |
l <key>元素
该元素用于指定父表中用作外键的数据列的名称,本例中,父表是ProbaServer,字段ServID是外键。
l <one-to-many>元素
该元素用于指定子对象的类型,必须是全限定名称,即包含程序集名称和命名空间。
l <many-to-one>元素
该元素用于指定父对象的类型,必须是全限定名称,即包含程序集名称和命名空间。
建立好了关联表间的映射关系,下面介绍如何使用这些映射关系进行级联操作。
(1) 级联添加
在保存父对象ProbaServer时,与之关联的子对象ProbaGateway也会同时保存,看示例代码如下:
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 开始一个事务
ITransaction transaction = session.BeginTransaction();
// 创建一个服务器对象,此时为非持久化对象
ProbaServer server = new ProbaServer();
server.ServStartTime = DateTime.Now;
server.ServIP = "192.168.1.200";
server.ServName = "算法服务器";
server.ServFun = "提供算法";
server.ServPlace = "二楼机房";
// 创建与服务器对应的网关
for (int i = 0; i < 3; i++)
{
ProbaGateway gateway = new ProbaGateway();
gateway.GwName = "网关" + i.ToString();
gateway.GwIP = "192.168.1." + i.ToString();
gateway.GwStartTime = DateTime.Now;
gateway.ProbaServer = server;
server.ProbaGateways.Add(gateway);
}
try
{
// 将服务器对象保存到数据库,持久化对象
session.SaveOrUpdateCopy(server);
// 提交事务
transaction.Commit();
Console.WriteLine("保存成功");
}
catch (Exception ex)
{
// 会滚事务
transaction.Rollback();
Console.WriteLine(ex.Message);
}
finally
{
// 关闭会话
session.Close();
}
(2) 级联更新
把父对象ProbaServer相关的部分子对象ProbaGateway移除,或者修改部分子对象,保存父对象时,与父对象相关的子对象也会更新。示例代码如下:
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 取得一个server
ProbaServer server = (ProbaServer)session.Load(typeof(ProbaServer), 23);
// 开始一个事务
ITransaction transaction = session.BeginTransaction();
try
{
server.ProbaGateways.RemoveAt(0);
ProbaGateway gateway = (ProbaGateway)server.ProbaGateways[0];
gateway.GwStartTime = DateTime.Now;
gateway.GwIP = "192.168.1.250";
session.Update(server);
transaction.Commit();
Console.WriteLine("更新成功");
}
catch (Exception ex)
{
// 会滚事务
transaction.Rollback();
Console.WriteLine(ex.Message);
}
finally
{
// 关闭会话
session.Close();
}
(3) 级联删除
把父对象ProbaServer删除的时候,与之相关联的子对象ProbaGateway也会删除。示例代码如下:
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 取得一个server
ProbaServer server = session.CreateCriteria(typeof(ProbaServer)).List()[0] as ProbaServer;
// 开始一个事务
ITransaction transaction = session.BeginTransaction();
try
{
session.Delete(server);
transaction.Commit();
Console.WriteLine("删除成功");
}
catch (Exception ex)
{
// 会滚事务
transaction.Rollback();
Console.WriteLine(ex.Message);
}
finally
{
// 关闭会话
session.Close();
}
NHibernate在载入父对象的时候,可以载入相应的对象,这种缺省的行为给级联操作带来了方便,例如,我们在程序中载入ProbaServer的对象时,可以通过它的ProbaGateways属性访问与之关联的所有子对象ProbaGateway。
但是,任何问题都有两面性,在提供方便的时候,它也降低了载入的效率。试想,如果ProbaGateway表还有下一级子表ProbaTerminal,假定ProbaServer与ProbaGateway的关系是1:10(即一台服务器下接10个网关),ProbaGateway与ProbaTerminal的关系也是1:10(即一个网关下连接10个设备),那么,在载入一个ProbaGateway对象的时候,实际就载入了111个对象,效率可想而知。
解决这个问题的方法就是延迟集合对象的载入,即延迟加载。在介绍<bag>元素的时候,提到过它的一个属性lazy,就是用来指定是否只用延迟加载的。要声明延迟载入一个集合对象,只需要在映射文件中<bag>元素添加一个属性:lazy=”true”。这样,直到程序中访问了这个集合时,才从数据库载入这个集合,同时,如果程序中从来不访问这个集合,这个集合就根本不被载入。
但是有一点限制,延迟载入需要从最初装载父对象的Session来载入,如果Session已经被关闭,而你的程序试图访问一个没有载入的集合,就会获得一个LazyInitializationException异常。示例代码如下:
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 取得一个server
ProbaServer server = (ProbaServer)session.Load(typeof(ProbaServer), 23);
// 关闭会话
session.Close();
try
{
ProbaGateway gateway = (ProbaGateway)server.ProbaGateways[0];
gateway.GwStartTime = DateTime.Now;
gateway.GwIP = "192.168.1.250";
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
当运行到try的第一个语句时,会抛出异常,得到以下信息:
NHibernate.LazyInitializationException: Failed to lazily initialize a collection
- no session
在 NHibernate.Collection.PersistentCollection.Initialize(Boolean writing)
在 NHibernate.Collection.PersistentCollection.Read()
在 NHibernate.Collection.Bag.get_Item(Int32 index)
在 NHTest.Program.Lazy() 位置 D:/piyeyong/工作目录/NHDoc/NHDoc/Program.cs:行
号 202
此时,可以将父对象与另一个Session关联,这是才可以继续加载子对象的集合。示例代码如下:
// 开始一个会话
ISession session = sessionFactory.OpenSession();
// 取得一个server
ProbaServer server = (ProbaServer)session.Load(typeof(ProbaServer), 23);
// 关闭会话
session.Close();
try
{
ISession session2 = sessionFactory.OpenSession();
session2.Lock(server, LockMode.None);
ProbaGateway gateway = (ProbaGateway)server.ProbaGateways[0];
gateway.GwStartTime = DateTime.Now;
gateway.GwIP = "192.168.1.250";
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
3.NHibernate的日志
几乎所有的大型应用都会有自己的用于跟踪调试的API。因为一旦程序被部署以后,就不可能再利用专门的调试工具了,我们可以用日志取而代之来监控应用程序的运行。日志记录是软件开发周期中重要的组成部分,它具有以下的优点:
l 提供应用程序运行时的精确环境,共程序开发人员尽快找到程序中Bug
l 程序运行时的日志信息能自动生成,无需人工干预
l 日志信息可以输出到磁盘等永久保存,供日后研究使用
幸运的是,NHibernate代码自身就集成了对日志的支持,它使用的是log4net来记录日志。程序开发人员只需要经过简单的配置,就可以让NHibernate输出日志信息供我们调试和研究之用。
按照以下步骤配置与NHibernate相关的log4net:
第一步,在App.config中添加配置信息:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<!-- This section contains the log4net configuration settings -->
<log4net debug="false">
<!-- Define some output appenders -->
<appender name="trace" type="log4net.Appender.TraceAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="console" type="log4net.Appender.ConsoleAppender, log4net">
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<appender name="rollingFile" type="log4net.Appender.RollingFileAppender,log4net" >
<param name="File" value="log.txt" />
<param name="AppendToFile" value="true" />
<param name="RollingStyle" value="Date" />
<param name="DatePattern" value="yyyy.MM.dd" />
<param name="StaticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
</layout>
</appender>
<!-- Setup the root category, add the appenders and set the default priority -->
<root>
<priority value="INFO" />
<appender-ref ref="rollingFile" />
</root>
</log4net>
</configuration>
此处的App.config文件略去了NHibernate相关的配置信息。它会记录所有INFO级别的日志,写到log.txt文件中去。
关于log4net的详细使用方法这里不进行描述。
第二步,在工程中引入log4net的动态链接库log4net.dll
第三步,在程序中加入如下代码:
log4net.Config.XmlConfigurator.Configure();
注意,一定要将这段代码加入到初始化NHIbernate之前,否则,在初始化NHiberante的过程中的日志信息就不会写出来了,如果初始化NHibernate出错,也就没有日志可查了。
4.其它注意事项
4.1 日期类型的映射
使用NHibernate是发现,日期字段如果没有初始化,保存记录时出现错误,查看log4net日志,找到如下信息:
2007-03-07 15:19:21,468 [3200] WARN NHibernate.Util.ADOExceptionReporter - System.Data.SqlTypes.SqlTypeException: SqlDateTime 溢出。必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间。
解决方法:日期字段用NullableDateTime类型。
步骤一,工程中引入Nullables.dll和Nullables.NHibernate.dll
步骤二,把所有持久化类定义中的DateTime改为Nullables.NullableDateTime
步骤三,把所有映射文件中日期对应的property元素的type属性改为Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate
注意,使用Nullable类型时必须使用NHibernate.dll的 1.0.3 版以上(含1.0.3版),否则会出现如下异常:
NHibernate.MappingException: could not interpret type: Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate
4.2 MS SQL Server 2000的Text字段映射