NHibernate是从Hibernate迁移到DotNet来的优秀的ORM框架,它把数据库中的表的关系模型转换为程序中的对象之间的关联模型,从而使程序模型更接近于现实中的逻辑。作为DotNet本身在VS2008 SP1中新增了ADO.NET Entity Framework,但EF非常庞大,远超过ORM的概念范畴,使初学者难以把握。而在VS2005中却没有这种ORM框架,在此我们先以NHibernate为工具研究ORM思想,并研究它如何在VS2005中应用。
ORM就是“对象-映射-关系”的简称,它主要包含三部分:
关系:关系型数据库。
对象:实体对象类
映射:XML文件,指定实体类与表的对应关系。
在这里需要我们操作的就有两点:编写实体对象类,配置映射文件。这两部份一般不需要我们手动编写,我们可以使用CodeSmith的模板来生成。
一、R-关系部份
以下面的数据库为例:
create database mydb
go
use mydb
go
--水果表
create table Fruit
(
Ids varchar(50) primary key,
[Name] varchar(50) not null,
Price decimal(8,2),
Source varchar(50),
Stack varchar(50),
Numbers int, --库存数量
[Image] varchar(50)
)
go
insert into fruit values('k001','苹果',2.4,'烟台','2',100,'image/0.gif')
insert into fruit values('k002','菠萝',1.4,'广东','3',100,'image/1.gif')
insert into fruit values('k003','桔子',2.4,'福州','3',100,'image/2.gif')
insert into fruit values('k004','葡萄',2.4,'新缰','2',100,'image/3.gif')
insert into fruit values('k005','樱桃',2.4,'青岛','4',100,'image/4.gif')
insert into fruit values('k006','桃子',2.4,'花果山','5',100,'image/5.gif')
insert into fruit values('k007','香蕉',2.4,'济南','5',100,'image/6.gif')
Fruit表是一个独立的单一表,我们以此表为例,研究如何配置ORM框架,并实现增、删、改、查功能
二、O-对象部份
第一步:新建一个类库Common
第二步:向Common项目中添加NHibernate.dll引用
第三步:使用CodeSmith执行NHibernate.cst文件生成实体类和映射文件
《图1》
Fruit.cs的文件代码如下:
using System;
using System.Collections;
using System.Web.UI.WebControls;
namespace Common
{
public class Fruit : System.IComparable
{
//成员变量
protected string _id;
protected string _name;
protected decimal _price;
protected string _source;
protected string _stack;
protected int _numbers;
protected string _image;
protected static String _sortExpression = "Id";
protected static SortDirection _sortDirection = SortDirection.Ascending;
//构造函数
public Fruit() { }
public Fruit( string name, decimal price, string source, string stack, int numbers, string image )
{
this._name = name;
this._price = price;
this._source = source;
this._stack = stack;
this._numbers = numbers;
this._image = image;
}
//属性
public virtual string Id
{
get {return _id;}
set
{
if ( value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Id", value, value.ToString());
_id = value;
}
}
public virtual string Name
{
get { return _name; }
set
{
if ( value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Name", value, value.ToString());
_name = value;
}
}
public virtual decimal Price
{
get { return _price; }
set { _price = value; }
}
public virtual string Source
{
get { return _source; }
set
{
if ( value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Source", value, value.ToString());
_source = value;
}
}
public virtual string Stack
{
get { return _stack; }
set
{
if ( value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Stack", value, value.ToString());
_stack = value;
}
}
public virtual int Numbers
{
get { return _numbers; }
set { _numbers = value; }
}
public virtual string Image
{
get { return _image; }
set
{
if ( value != null && value.Length > 50)
throw new ArgumentOutOfRangeException("Invalid value for Image", value, value.ToString());
_image = value;
}
}
public static String SortExpression
{
get { return _sortExpression; }
set { _sortExpression = value; }
}
public static SortDirection SortDirection
{
get { return _sortDirection; }
set { _sortDirection = value; }
}
//实现IComparable接口的排序方法
public virtual int CompareTo(object obj)
{
if (!(obj is Fruit))
throw new InvalidCastException("This object is not of type Fruit");
int relativeValue;
switch (SortExpression)
{
case "Id":
relativeValue = this.Id.CompareTo(((Fruit)obj).Id);
break;
case "Name":
relativeValue = this.Name.CompareTo(((Fruit)obj).Name);
break;
case "Price":
relativeValue = (this.Price != null) ? this.Price.CompareTo(((Fruit)obj).Price) : -1;
break;
case "Source":
relativeValue = (this.Source != null) ? this.Source.CompareTo(((Fruit)obj).Source) : -1;
break;
case "Stack":
relativeValue = (this.Stack != null) ? this.Stack.CompareTo(((Fruit)obj).Stack) : -1;
break;
case "Numbers":
relativeValue = (this.Numbers != null) ? this.Numbers.CompareTo(((Fruit)obj).Numbers) : -1;
break;
case "Image":
relativeValue = (this.Image != null) ? this.Image.CompareTo(((Fruit)obj).Image) : -1;
break;
default:
goto case "Id";
}
if (Fruit.SortDirection == SortDirection.Ascending)
relativeValue *= -1;
return relativeValue;
}
}
}
三、M-映射部分
Fruit.hbm.xml映射文件如下(在这里我用的是nhibernate2.2):
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<!--Common.Fruit类与Fruit表进行映射-->
<class name="Common.Fruit, Common" table="Fruit">
<!--主键Id映射到Ids列-->
<id name="Id" type="String" unsaved-value="null">
<column name="Ids" length="50" sql-type="varchar" not-null="true" unique="true" index="PK__Fruit__7C8480AE"/>
<generator class="native" />
</id>
<!--列映射-->
<property name="Name" type="String">
<column name="Name" length="50" sql-type="varchar" not-null="true"/>
</property>
<property name="Price" type="Decimal">
<column name="Price" length="5" sql-type="decimal" not-null="false"/>
</property>
<property name="Source" type="String">
<column name="Source" length="50" sql-type="varchar" not-null="false"/>
</property>
<property name="Stack" type="String">
<column name="Stack" length="50" sql-type="varchar" not-null="false"/>
</property>
<property name="Numbers" type="Int32">
<column name="Numbers" length="4" sql-type="int" not-null="false"/>
</property>
<property name="Image" type="String">
<column name="Image" length="50" sql-type="varchar" not-null="false"/>
</property>
</class>
</hibernate-mapping>
第四步:把Fruit.hbm.xml文件作为嵌入资源,否则找不到该文件,就无法识别我们的实体对象类。
到此为至,我们的O、R、M三部份都实现完成了,下面的内容就是配置NHibernate引擎。
第五步:编写hibernate.cfg.xml配置文件,并把该文件入在网站的BIN文件夹中,否则也会找不到该文件。
四、NHibernate的配置部份
<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
<session-factory>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">
server=.;database=mydb;uid=sa;pwd=123;
</property>
<property name="adonet.batch_size">10</property>
<property name="show_sql">true</property>
<property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
<property name="use_outer_join">true</property>
<property name="command_timeout">10</property>
<property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
<mapping assembly="Common"/>
</session-factory>
</hibernate-configuration>
hibernate.cfg.xml中配置了NHibernate的数据库链接,Sql方言相关信息。
到目前为止所有的配置信息都已完成,剩下的内容就是编写代码访问数据库了。
NHibernate访问数据库常用的有两个接口:ISessionFactory和ISession。
ISessionFactory接口:负责初始化NHibernate。它充当数据存储源的代理,并负责创建Session对象。这里用到了工厂模式。一般情况下,一个项目通常只需要一个SessionFactory就够,当需要操作多个数据库时,可以为每个数据库指定一个SessionFactory。
Session接口:负责执行被持久化对象的CRUD操作(增、删、改、查)。但ISession是非线程安全的。NHibernate的ISession不同于HTTP应用中的HttpSession,我们将HttpSesion对象称为用户session。
五、使用NHibernate访问数据库
第六步:在Common项目中建立SessionManager类
using System;
using System.Collections.Generic;
using System.Text;
using NHibernate;
using NHibernate.Cfg;
namespace Common
{
public class SessionManager
{
private ISessionFactory _SessionFactory;
private ISession _Session;
public SessionManager()
{
//生成SessionFactory
_SessionFactory = new Configuration().Configure().BuildSessionFactory();
//使用SessionFactory对象生成Session对象
_Session = _SessionFactory.OpenSession();
}
//返回Session对象
public ISession Session
{
get
{
return _Session;
}
}
}
}
该类的作用就是生成Session对象,以便对数据库的操作。
第七步:创建数据库访问代码
using System;
using System.Collections.Generic;
using System.Text;
using NHibernate;
namespace Common
{
public class FruitDA
{
private ISession _Session;
public FruitDA()
{
//生成ISession对象
_Session = new SessionManager().Session;
}
public IList<Fruit> Select()
{
//使用ISession对象的CreateQuery()方法来执行HQL查询数据库,并返回泛型集合数据
IList<Fruit> list = _Session.CreateQuery("from Fruit").List<Fruit>();
return list;
}
}
}
第八步:页面调用FruitDA类的Select()方法查询数据,并显示在页面上
GridView1.DataSource = new FruitDA().Select();
GridView1.DataBind();
好了,使用NHibernate建立一个简单查询就完成了。
NHibernate与ADO.NET访问数据库
NHibernate只需要我们编写好实体类和映射文件就可以了,当然这部份代码我们可以使用CodeSmith来生成,一旦配置好NHibernate后,所以的数据库访问都由NHibernate代我们完成。作为开发人员,我们只需要理清商业逻辑,管理好我们的实体对象就可以了,不用再使用SqlConnection,SqlCommand等对象去为每个表编写枯燥数据库操作类了。
NHibernate与Linq To Sql
从易用性上来讲,NHibernate确实不如Linq To Sql容易使用,在实体类的生成和映射文件的生成中Linq To Sql只需要点点鼠标就可以实现了。但Linq To Sql是在VS2008中提供出来的,在VS2005中无法使用的。另外,对数据量不太大的情况下,Linq To Sql的性能不如NHibernate高,在数据量大的情况下,Linq To Sql则表现出强悍的性能。另外Linq To Sql目前只适用于SqlServer数据库,还不能算是一个完善的ORM框架。