前面随意到网站抄了一篇叫做
《NHibernate入门经典》文章(http://blog.youkuaiyun.com/rocket5725/archive/2009/06/03/4240240.aspx)
准备用来学习NHibernate,但是发现总感觉不是那么回事,所以自己重新写一篇《NHibern-ate深入编程》,希望对优快云做点贡献,当然在这篇文章之前,我还没有见到NHibernate的强大所在,希望在完成本文的时候可以把调试结果贴到文章中让大家看到。
首先介绍开发环境:VS2005+SQL2000(使用数据库Northwind)+NUnit2.4.8+NHibernate1.2如果大家版本有所不同,请注意查看本文后面的异常处理章节。
在进行NHibernate前,先在VS2005中创建控制台应用程序->NHibernateTest工程,然后添加引用(log4net.dll、NHibernate.dll、nunit.framework.dll这是至关重要的,否则你无法进行类的操作),并在右击工程->属性->调试栏目中->选中启动外部程序->D:/Program Files/NUnit 2.4.8/bin/nunit.exe保存即可,这样可以使得Nunit和你的工程联系起来(如图1所示)。
一 NHB简介
NHB是基于ms.net的O/R Mapping持久框架,它从基于Java的Hibernate项目移植而来。O/R Mapping就是把对象到映射关系数据库的记录,简单的说就是能实现把一个对象存储为数据表中的一条记录和由一条记录创建一个相应的对象,数据表中的数据就是对象的属性。
那么为什么要使用O/R Mapping?它与传统的DataSet/DataTable又有什么不同了?
首先是设计上的不同,当使用O/R Mapping时,更多的是从对象的角度来设计程序,而把数据(对象的属性)存储的细节放在后面, 可以完全采用面向对象(OO)的方式来设计,而在使用DataSet/DataTable时,它只是存放数据的对象,看起来更像一个数据表,不能直观的表达业务概念。
二 NHB中主要接口的介绍
ISession
ISession是面向用户的主要接口,主要用于对象持久化,数据加载等操作,支持数据库事务,它隐藏了NHB内部复杂的实现细节,ISession由ISessionFactory创建。
ISessionFactory
ISessionFactory是NHB内部的核心类,它维护到持久机制(数据库)的连接并对它们进行管理,同时还会保存所有持久对象的映射信息。
ISessionFactory由Configuration创建,因为创建ISessionFactory的开销非常大(需要加载映射信息),所以这个对象一般使用Singleton(单例)模式。
ITransaction
ITransaction是NHB的事务处理接口,它只是简单的封装了底层的数据库事务。
事务必须由ISession来启动。
ICriteria
ICriteria是Expression(表达式)数据加载接口,Expression是一个关系表达式组合,通过它能产生SQL语句的Where部分, 用户需要通过ISession来间接调用它。
IQuery
IQuery是HQL数据加载接口,HQL(Hibernate Query Language)是NHB专用的面向对象的数据查询语言,它与数据库的SQL有些类似,但功能更强大!同ICriteria一样,也需要通过ISession来间接调用它。
三 持久化操作
1. 会话和会话工厂
要进行持久化操作,必须先取得ISession和ISessionFactory,我们用一个Sessions类来封装它们, Sessions类的属性和方法都是静态的,它有一个Factory属性, 用于返回ISessionFactory, 有一个GetSession方法,用于取得一个新的ISession。
测试类(SessionsFixture.cs)代码如下:
现在还没写Sessions类,将不能通过编译! 下面我们来实现Sessions类.Sessions.cs代码如下:
OK,现在编译可以通过了,启动NUnit并选择生成的文件NHibernateTest.exe,运行测试。我们得到了红色的条,出错了!
提示信息如图2所示,根据提示信息可以判断原来还没有加入NHibernate的配置信息(当使用NHibernate时,需要在项目的配置文件中加入NHibernate的配置信息。关于配置信息,在下面有说明)。在项目的配置文件App.Config(如没有请自行创建一个)中加入以下内容.
稍做解释,配置文件App.Config主要是配置了NHibernate和log4net,例如配置了NHibernate连接数据库的连接字符串;log4net把日志记到哪个文件中,本例就是"log.txt"。每次写日志向文件中追加,而不是重写.具体代码如下:
再次运行测试,就可以看见绿色的条了。
在取得会话工厂的代码中,我使用了如下代码,这是一个典型的double lock方式,用来产生线程安全的Singletion(单例)对象。
2. 基本CRUD操作
在很多介绍NHB的文章,包括NHB带的测试用例中,业务对象只是做为一个数据实体存在的,它没有任何操作!这在java中是比较典型的作法。而我希望我们的业务对象自身就能完成基本的Create/Retrieve/Update/Delete,即CRUD操作。在罗斯文商贸应用中,存在客户(customer)业务对象,先来为它建立一个测试用例CustomerFixture.cs。
接下来创建Customer业务类:
在Customer类中,没有实现CRUD操作,这些操作在业务对象基类BizObject中实现,代码如下:
BizObject简单的将数据操作转发至ObjectBroker类, 目的是为了降低业务层和NHB之间的耦合, 以利于持久层间的移植。
ObjectBroker对ISession进行了必要的封装,通过ISession,就可以简单的完成对象的CRUD操作了。
编译并运行测试,CustomerFixture的TestCRUD操作还是不能通过! 异常信息为:
NHibernateTest.CustomerFixture.TestCRUD : NHibernate.MappingException : Unknown entity class: NHibernateTest.Customer
显然,是因为我们还没有为Customer对象编写映射文件,而导致NHB不能对Customer对象进行持久化操作。
Customer对象的映射文件(Customer.hbm.xml)内容如下:
(注意由于NHibernate版本不同内容有差异,如果1.0之前的xmlns="urn:nhibernate-mapping-2.0",否则为2.2)
这个映射文件算是NHB中较为简单的了。class的name指定业务对象全名及其所在程序集,table指定数据表的名称;id用于指定一个对象标识符(数据表中的主键)及其产生的方式, 常用的主健产生方式有自增型(identity)和赋值型(assigned),这里使用了assigned,需要注意的是unsaved-value属性,它指定对象没有持久化时的Id值,主要用于SaveOrUpdate操作;property用于指定其它映射的数据列;在id和property中,name指定属性名称,column指定数据列的名称,type指定属性类型,注意这里的类型是NHB中的类型,而不是.NET或数据库中的数据类型。
另外,对象映射文件名称请按”对象名.hbm.xml”的规范来命名, 最后在映射文件的属性中把操作改为"嵌入的资源"(特别注意这点很重要,我就是没有注意到这。郁闷了很久啊!!!)。
现在重新编译程序并运行测试,就能看到绿条了!如果出现以下的异常信息,如图3所示。解决方案为:
把对象.hbm.xml中的相应行改为:<class name="NHibernateTest.Customer,NHibernateTest" table="Customers" lazy="false">(相当重要)
因为Product对象将在后面的案例中多次使用,在这里按与Customer相同的步骤创建它。
// Product单元测试ProductFixture.cs
Product对象(注意,注释部分并不是无意义的,是在不同的情况下做测试用的,本文中的注释都基本如此)
映射文件Product.hbm.xml
注意创建Category类
编译并运行测试,检查错误直到单元测试通过。
注意:因为在数据库中,products表与categories表、suppliers表有外键约束,必须先删除这两个约束,product测试用例才能通过,后面我们再加上这两个约束。
现在我们已经掌握了NHB的基本CRUD操作了,整个过程应该说是比较简单吧。呵呵,不再需要使用Connection、DataAdapter、DataSet/DataReader之类的对象了,下面继续学习NHB中更为复杂的映射关系。
3. one-to-one
一对一是一种常见的数据模型,它有两种情况:一种是主键(PrimaryKey)关联;另一种是外健(ForeignKey)关联,在使用外健的时候要保证其唯一性。
在主键关联的情况下, 必须有一个主键是根据别一个主键而来的。NHB是通过一种特殊的方式来处理这种情况的, 要注意两个主健名称必须同名,而外健关联需要在one-to-one配置中定义一个property-ref属性, 这个属性在当前版本的NHB(这是指的是nhibernate 0.5.3,当前的版本是什么情况我现在也没有时间了解)中还没有实现。
在罗斯文商贸应用,不需要使用one-to-one映射,这里先不对其进行讲解,如欲了解one-to-one方面的应用,请参考我网站上的文章。
4. many-to-one
many-to-one是描述多对一的一种数据模型,它指定many一方是不能独立存在的,我个人认为many-to-one是NHB中保证数据有效性的最有用的一种映射,通过使用many-to-one能有效的防治孤儿记录被写入到数据表中。
在罗斯文商贸数据中,Product(产品)与Category(类别)是多对一的关系。下面我们来处理这一映射关系,
首先要让Category能实现基本的CRUD操作,步骤同上, 这里只列出测试用例,类和映射文件请参照上面的方式创建。
上面的测试用例通过后,接着修改Product的各部分。
Product测试用例修改如下:
Product类做如下修改:
1. 删除categoryId 字段和CategoryId属性;
2. 加入以下代码:
public Category Category {
get { return _category; }
set { _category = value; }
}
private Category _category;
Product映射文件做如下修改:
class="NHibernateTest.Business.Category, NHibernateTest" />
这里用到了一个many-to-one标签,用于指定与one的一方进行关联的对象信息。
name指定one一方在对象中的名称;
column指定映射数据列的名称;
unique指定one一方是唯一的;
class 指定one一方类的全名,包括程序集名称;
重新编译程序,运行测试用例, 看到绿条了吗?没有就根据异常去除错吧!我已经看到绿条了。:)
声明: 因为过多的many-to-one使后面的测试代码变得异常庞大(创建对象时要创建one一方的类),所以在后面的代码中,我假定Product对象是没有实现任何many-to-one映射的。如果不怕麻烦,请自行修改后面测试用例。
注意:如果想看运行的结果,可以再Main函数中添加代码进行测试,然后可以再SQL 2000的相对应的数据表中看到数据的变化。
执行下面的程序后,用户就可以看到插入到数据表中的数据了,这样NHibernate的初次使用就成功啦。
截屏表示祝贺