初学NHibernate,参照李永京的“NHibernate之旅系列文章”,文章写得比较详细,只是里面提供的例子和讲的有些出入,并且依据的NHibernate版本是3.0.0.4000,而现在最新版本是3.3.2.4000,导致程序有所不同。以下将摘抄全部原文“NHibernate之旅(2):第一个NHibernate程序”并以红色字体进行补充。
本节内容
- 开始使用NHibernate
- 1.获取NHibernate
- 2.建立数据库表
- 3.创建C#类库项目
- 4.设计Domain
- 4-1.设计持久化类
- 4-2.编写映射文件
- 5.数据访问层
- 5-1.辅助类
- 5-2.编写操作
- 6.数据访问层的测试
- 6-1.配置NHibernate
- 6-2.测试
- 结语
作者注:2009-11-06已更新
开始使用NHibernate
我们亲自动手,一步一步搭建一个NHibernate程序来,我以一个实际场景电子交易程序来模拟,客户/订单/产品的经典组合。由于是第一次使用NHibernate,所以我们的目的是映射一张表并完成使用NHibernate来读取数据,下面的一幅图片给了我们第一印象。我们按照基本开发软件思想的流程一步一步完成。
我使用的开发环境:Microsoft Visual Studio 2008 SP1、SQL Server 2008 Express、NHibernate 2.1.1GA。
1.获取NHibernate
使用官方2009年10月31日最新发布的NHibernate-2.1.1.GA版本。如果你第一次使用NHibernate,先到这里下载NHibernate最新版本(包括源码、发布版本、参考文档、API文档,可选择下载)。如果用到NHibernate的扩展项目到这里下载获得NHibernate Contrib最新版本。NHibernate-2.1.1.GA是.NET2.0平台的最后一个版本,关于NHibernate-2.1.1.GA的更多信息请点击这里。
关于NHibernate2.1版本的一些说明:
NHibernate2.1版本改变了ByteCode延迟加载机制,有三种3种IoC框架动态代理方式,分别为:Castle框架、LinFu框架、Spring.Net框架。我们只要选择一种,在配置文件中配置proxyfactory.factory_class节点。
如果使用Castle.DynamicProxy2动态代理,引用NHibernate.ByteCode.Castle.dll程序集并配置proxyfactory.factory_class节点为<property name="proxyfactory.factory_class"> NHibernate.ByteCode.Castle.ProxyFactoryFactory,NHibernate.ByteCode.Castle</property>
如果使用LinFu.DynamicProxy动态代理,引用NHibernate.ByteCode.LinFu.dll程序集并配置proxyfactory.factory_class节点为<property name="proxyfactory.factory_class"> NHibernate.ByteCode.LinFu.ProxyFactoryFactory,NHibernate.ByteCode.LinFu</property>
如果使用Spring.Aop动态代理,引用NHibernate.ByteCode.Spring.dll程序集并配置proxyfactory.factory_class节点为<property name="proxyfactory.factory_class"> NHibernate.ByteCode.Spring.ProxyFactoryFactory,NHibernate.ByteCode.Spring</property>
另外NHibernate2.1要求.NET2.0 SP1以上版本 (System.DateTimeOffset),请使用VS2005的,务必打上Sp1补丁。推荐使用VS2008以上版本。
目前最新版本号:3.3.2.4000(2013.2.1)
2.建立数据库表
由于第一次使用,还是按照我们传统的从数据库表配置吧。
打开SQL Server Management Studio Express,新建一个新的数据库NHibernateSample,创建四个表:分别为客户表、订单表、订单产品表、产品表。CustomerId数据类型为int 自增,也可以非自增,NHibernate会按照内置的主键生成器策略在持久化的时候为对象生成一个ID
3.创建C#类库项目
由于是我们第一个程序,所以我没有按照Domain Driver Design方法去设计这个程序,按照大家的常规思想来实现的,以后有机会再介绍Domain Driver Design设计。
使用VS2008创建C#类库的项目,命名为NHibernateSample。打开项目文件夹,在其项目文件目录上新建SharedLibs文件夹,把下载NHibernate相关程序集文件拷贝到SharedLibs文件夹下。(所有要引用的dll在同一目录下便于管理控制)如下图,这里我选择Castle框架动态代理:
创建项目,结构如下:
- Domain(领域模型):用于持久化类和O/R Mapping操作
- Data(Data Access Layer数据访问层):定义对象的CRUD操作
- Data.Test(数据访问层测试):对数据访问层的测试,这里我使用Nunit单元测试框架
- Web:Web页面(这篇文章中暂未实现,请参考我的博客其他文章)
(Data.Test和Web此处用一个名为NHibernateSample的Winform项目代替)
4.设计Domain
4-1.编写持久化类
按简单传统.NET对象(POCOs,Plain Old CLR Objects(Plain Ordinary CLR Objects))模型编程时需要持久化类。在NHibernate中,POCO通过.NET的属性机制存取数据,就可以把它映射成为数据库表。
现在为Customer编写持久化类来映射成为数据库表。新建一个Customer.cs类文件:
namespace NHibernateSample.Domain.Entities { public class Customer { public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } } }
4-2.编写映射文件
NHibernate要知道怎样去加载和存储持久化类的对象。这正是NHibernate映射文件发挥作用的地方。映射文件包含了对象/关系映射所需的元数据。元数据包含持久化类的声明和属性到数据库的映射。映射文件告诉NHibernate它应该访问数据库里面的哪个表及使用表里面的哪些字段。
这里,我为Customer.cs类编写映射文件。新建一XML文件,命名为Customer.hbm.xml:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateSample.Domain" namespace="NHibernateSample.Domain.Entities"> <class name ="Customer"> <id name="Id" column ="CustomerId"> <generator class ="native"/> </id> <property name ="FirstName"/> <property name ="LastName"/> </class> </hibernate-mapping>
5.编写数据访问层
5-1.辅助类
我们现在可以开始NHibernate了。首先,我们要从ISessionFactory中获取一个ISession(NHibernate的工作单元)。ISessionFactory可以创建并打开新的Session。一个Session代表一个单线程的单元操作。 ISessionFactory是线程安全的,很多线程可以同时访问它。ISession不是线程安全的,它代表与数据库之间的一次操作。ISession通过ISessionFactory打开,在所有的工作完成后,需要关闭。 ISessionFactory通常是个线程安全的全局对象,只需要被实例化一次。我们可以使用GoF23中的单例(Singleton)模式在程序中创建ISessionFactory。这个实例我编写了一个辅助类NHibernateHelper 用于创建ISessionFactory并配置ISessionFactory和打开一个新的Session单线程的方法,之后在每个数据操作类可以使用这个辅助类创建ISession 。
public class NHibernateHelper { private ISessionFactory _sessionFactory; public NHibernateHelper() { _sessionFactory = GetSessionFactory(); } private ISessionFactory GetSessionFactory() { return (new Configuration()).Configure().BuildSessionFactory(); } public ISession GetSession() { return _sessionFactory.OpenSession(); } }
5-2.编写操作
在Data中新建一类NHibernateSample.cs,编写一方法GetCustomerId用于读取客户信息。在编写方法之前,我们需要初始化Session。
protected ISession Session { get; set; } public NHibernateSample(ISession session) { Session = session; }
NHibernate有不同的方法来从数据库中取回对象。最灵活的方式是使用NHibernate查询语言(HQL),是完全基于面向对象的SQL。
public void CreateCustomer(Customer customer) { Session.Save(customer); Session.Flush(); } public Customer GetCustomerById(int customerId) { return Session.Get<Customer>(customerId); }
6.编写数据访问层的测试
6-1.配置NHibernate
我们可以几种方法来保存NHibernate的配置,具体以后来介绍,这里我们使用hibernate.cfg.xml文件来配置,不过不必担心,这个文件我们可以在src\NHibernate.Config.Templates文件夹下找到,直接复制到Data.Test中修改一下配置信息和文件输出属性就可以了。
<?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"> Data Source=.\SQLEXPRESS;Initial Catalog=NHibernateSample; Integrated Security=True;Pooling=False </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> <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property> <mapping assembly="NHibernateSample.Domain"/> </session-factory> </hibernate-configuration>
补充说明:由于上述配置文件是根据NHibernate 3.0.0.4000写的,现在使用的是NHibernate 3.3.2.4000,所以需要改写配置文件中若干:
1、<property name="use_outer_join">true</property>
错误提示:An exception occurred parsing configuration :“name”特性无效 - 根据数据类型“String”,值“use_outer_join”无效 - 枚举约束失败。
解决办法:去掉该配置行
2、<property name="proxyfactory.factory_class">
NHibernate.ByteCode.Castle.ProxyFactoryFactory,
NHibernate.ByteCode.Castle
</property>
错误提示:
Unable to load type 'NHibernate.ByteCode.Castle.ProxyFactoryFactory,
NHibernate.ByteCode.Castle' during configuration of proxy factory class.
Possible causes are:
- The NHibernate.Bytecode provider assembly was not deployed.
- The typeName used to initialize the 'proxyfactory.factory_class' property of the session-factory section is not well formed.
Solution:
Confirm that your deployment folder contains one of the following assemblies:
NHibernate.ByteCode.LinFu.dll
NHibernate.ByteCode.Castle.dll
解决办法:NHibernate 3.2版本已经集成了Proxy,所以不再需要NHibernate.ByteCode.LinFu.dll 或者 NHibernate.ByteCode.Castle.dll
同时,将上述配置行改为:<property name="proxyfactory.factory_class">NHibernate.Bytecode.DefaultProxyFactoryFactory, NHibernate</property>
6-2.测试
好了,终于可以使用我们的方法了,这里新建一个测试类NHibernateSampleFixture.cs来编写测试用例:调用NHibernateSample类中GetCustomerId方法查询数据库中CustomerId为1的客户,判断返回客户的Id是否为1。
[TestFixture] public class NHibernateSampleFixture { private NHibernateSample _sample; [TestFixtureSetUp] public void TestFixtureSetup() { _sample = new NHibernateSample(); } [Test] public void GetCustomerByIdTest() { var tempCutomer = new Customer {FirstName = "李", LastName = "永京"}; _sample.CreateCustomer(tempCutomer); Customer customer = _sample.GetCustomerById(1); int customerId = customer.Id; Assert.AreEqual(1,customerId); } }
注明:此处用名为NHibernateSample的Winform项目进行测试。
窗体上一个按钮触发存、读动作。我的项目有问题,所以下面代码中的名称空间引用会和以上表述关系不符,注意修改。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private NHibernateSample.Data.NHibernateSample _sample;
public void TestFixtureSetup()
{
NHibernateHelper nhibernateHelper = new NHibernateHelper();
_sample = new NHibernateSample.Data.NHibernateSample(nhibernateHelper.GetSession());
}
public void GetCustomerByIdTest()
{
var tempCutomer = new Customer { FirstName = "李", LastName = "永京" };
_sample.CreateCustomer(tempCutomer);
Customer customer = _sample.GetCustomerById(1);
MessageBox.Show( customer.Id.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
this.TestFixtureSetup();
this.GetCustomerByIdTest();
}
}
运行过程曾出现能多次插入但只能一次正确运行_sample.GetCustomerById(1);
错误提示:could not load an entity: [NHibernateSample.Data.Entities.Customer#4][SQL: SELECT customer0_.CustomerId as CustomerId0_0_, customer0_.FirstName as FirstName0_0_, customer0_.LastName as LastName0_0_ FROM Customer customer0_ WHERE customer0_.CustomerId=?]
原因:数据库中字段CustomerId错写为CusomerId,所以需要注意配置文件和数据库字段的一致性,另外数据库中字段大小写与配置文件可以不匹配
我们使用TestDriven.NET测试一下这个方法:OK,测试通过。这里我使用NHibernate监视器NHibernate Profiler查看结果: