NHibernate Step by Step:序篇

很久以前,你可能习惯了connection.open,command.execute("select...")这样的代码,写啊写啊,你开始不停地copy/paste,因为数据库的操作太雷同了,还好,粘粘贴贴倒也凑合,然后有一天客户说他们要用oracle,于是你开始抱着本oracle开发指南狂啃pl/sql,你开始试验调用oracle的存储过程该怎么写,参数是用@还是?或者是:,取个blob游标又要咋写,你纳闷为啥这不同的数据库sql区别咋这么大呢?不都是号称符合啥sql几几的标准吗?你的头开始痛了,你开始写不同的DAL,代码与十指齐飞,脑袋昏昏天黑黑——又加班了!!这次第,怎一个愁字了得!!
然后你觉得再也不能这样过了,再也不能这样活了,你开始用DAAB,后来升级到Enterprise Library,恩,不错,代码好象少了,后来再加个Code Smith,恩,不错,自动生成代码,自己敲键盘少了,虽然你还得在不同的数据库间挣扎,还得把取出来的数据一点一点地填到你的实体类里,但是目前看上去还不赖,可以忙里偷闲泡杯两块八一包的劣质茶叶咂吧咂吧你那被劣质显示器辐射的开裂的嘴唇了。但是你觉得还不够,你在想,是否有Code Neo,或者干脆来个Code Matrix,整个DAL都不用自己写了,自动把数据库的数据填到自己的实体类里,然后对这个对象进行操作就行了,剩下的CRUD全部有这个Code Matrix来完成,达不达??
神啊,救救我吧……

“当当当当……“,O/R Mapping来到了大家的眼前。
O/R Mapping全称是object/rational mapping,即对象/关系数据库映射,意思是对象模型和关系模型的映射,也就是把我们常见的以对象模型表示的对象映射到关系模型上去,当然,这个关系模型我们常常指的是目前大部分主流的关系数据库,如oracle/sqlserver等等。基于.Net来说,o/r mapping就是将我们的.Net类映射到数据库中指定的表上,由O/R Mapping框架帮我们实现object<-->table之间的交互,我们完全不再需要写sql语句(当然不是绝对的),也不再关心背后用的是那种数据库,你只需要定义好对象和数据库该如何交互,剩下的,全部由O/R Mapping的框架来解决,你只需要如下代码即可:
Customer cust = framework.Get(...);
cust.Name = "newname";
framework.Save(cust);
或者:
framework.Delete(cust);
ok!!手起键盘落,整个世界清净了!!

是不是很动心啊??
ok,正式介绍今天的主角——Hibernate!!
Hibernate是一个目前应用的最广泛的开放源代码的对象关系映射框架,它对Java的JDBC(类似于ADO.Net)进行了非常轻量级的对象封装,使得程序员可以随心所欲的使用对象编程思维来操纵数据库,目前在国内Java开发界已经颇为流行,Hibernate+Spring往往是很多Java公司招聘的要求。而NHibernate,顾名思义,如同NUnit,NAnt一样,是基于.Net的Hibernate实现,但是目前介绍NHibernate的资料非常少,缺少一个系统完整的教程来全面的展现和深入NHibernate,而且现在NHibernate的文档又残缺不全,少的可怜,很多NHibernate的学习者往往都是通过Hibernate的文档来学习,但是毕竟不是所有的.Net开发者都熟悉Java,也不是所有的人都有精力有时间去学习Java,所以,我准备开始一个Step by Step的NHibernate教程,以便有兴趣的朋友能够快速的熟悉NHibernate,能够更快地体验NHibernate的开发乐趣。
我将会从零开始,配以详细的代码和图片,以便感兴趣的朋友能够最快最清晰地掌握NHibernate。

NHibernate Step by Step (一) Hello,NHibernate!

NHibernate Step by Step (一)  Hello,NHibernate!
 好了,今天我们正式开始NHibernate的历程,在第一次的练习中,我将尽量详细地讲解环境的配置,以后将不再详细解释。

基本的软件环境如下:
1.NHibernate http://www.nhibernate.org/ 当前版本是1.0.2
2.Code Smith http://www.codesmithtools.com/
3.NHibernate模板 点击这里下载
当然,少不了VS2005跟SQLServer了,我这里用的是SQLServer2005,教程用在SQLServer2000上应该没有问题,默认情况下,我将建立并使用一个叫NHibernate的数据库。

首先,我们先建立一个最简单的Person表,如下完整脚本(你可以进行修改以适合自己的数据库):

 

USE [NHibernate]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Person](
    [id] [
int] IDENTITY(1,1) NOT NULL,
    [name] [varchar](
50) COLLATE Chinese_PRC_CI_AS NOT NULL,
 CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (IGNORE_DUP_KEY 
= OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

 

仅有两个字段,一个自动增长的id,一个name,如下:

 
然后将下载的nhibernate-template解压,打开Code Smith,将模板加入”Template Explorer”,如下:

 
然后在其中的NHibernate.cst上点右键,选择“Execute”,弹出设置窗口,在左边的属性窗口进行如下设置:

 
注意:SourceDatabase属性在第一次选择时需要配置一个连接字符串,配置好后Code Smith将记录下来。 Assembly属性代表的是生成文件的默认Assembly名,而NameSpace,顾名思义,就是使用的命名空间了,这里我们全部使用”Test.Model”,请记住这个名字,点击左下角的Generate,将会在指定的输出目录下产生两个文件:Person.cs,Person.hbm.xml。

好了,NHibernate需要的类文件和映射文件生成完了,我们可以开始干活了!(生成NHibernate文件均是如此步骤,以后不再赘述)

新建立一个类库工程,为了简洁起见,我们命名为Model,需要注意的是,为了跟刚才生成的文件对应,我们需要在Model工程的属性页中将起Assembly名字设为上面的“Test.Model”,如下:

 
然后将刚才生成的两个文件Person.cs和Person.hbm.xml加入到Model工程中来,选中Person.hbm.xml文件,在属性窗口中将其“Build Action”设置为“Embedded Resource”(这是非常重要的一步,否则NHibernate将无法找到映射文件),如下:
 

build,ok,通过。

然后建立一个控制台工程,命名为Console1,添加NHibernate和上面Model项目的引用,另外添加一个应用程序配置文件,如下:

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<configSections>
    
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System,
                    Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089"
 />
  
</configSections>

  
<nhibernate>
    
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />
    
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" />
    
<add key="hibernate.connection.connection_string" value="Server=localhost;Initial Catalog=NHibernate;Integrated Security=SSPI" />
    
<add key="hibernate.connection.isolation" value="ReadCommitted"/>
    
<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" />
  
</nhibernate>

</configuration>

 


然后编写如下代码:

 

using System;
using System.Collections.Generic;
using System.Text;
using NHibernate;
using NHibernate.Cfg;
using Test.Model;

namespace Console1
{
    
class Program
    
{
        
static void Main(string[] args)
        
{
            Configuration config 
= new Configuration().AddAssembly("Test.Model");
            ISessionFactory factory 
= config.BuildSessionFactory();
            ISession session 
= factory.OpenSession();

            Person person 
= new Person();
            person.Name 
= "Jackie Chan";

            ITransaction trans 
= session.BeginTransaction();
            
try
            
{
                session.Save(person);
                trans.Commit();
                Console.WriteLine(
"Insert Success!");
            }

            
catch (Exception ex)
            
{
                trans.Rollback();
                Console.WriteLine(ex.Message);
            }

        }

    }

}

 


运行,ok,执行成功!!
我们到数据库检查一下,如下:

 
我们想要添加的记录已经成功加入到数据库中!!
是不是感觉有些神奇啊?好,我们开始详细解释。
先来看生成的两个文件,第一个是Person.cs,如下:

 

using System;
using System.Collections;

namespace Test.Model
{
    
Person
}

 


你可以发现,这完全是一个普通的poco类(Plain Old CLR Object),仅仅是对数据库person表的一个完全映射,不依赖于任何框架,可以用来作为持久化类,你可以在任何地方使用而不用担心依赖于某些神秘的运行时东西。

另外,NHibernate需要知道怎样去加载(load)和存储(store)持久化类的对象。这正是NHibernate映射文件发挥作用的地方。映射文件告诉NHibernate它应该访问数据库(database)里面的哪个表(table)及应该使用表里面的哪些字段(column),这就是我们今天要讲的重点了,Person.hbm.xml,如下:

 

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
 
<class name="Test.Model.Person, Test.Model" table="Person">
  
<id name="Id" type="Int32" unsaved-value="0">
   
<column name="id" sql-type="int" not-null="true" unique="true" index="PK_Person"/>
   
<generator class="native" />
  
</id>
  
<property name="Name" type="String">
   
<column name="name" length="50" sql-type="varchar" not-null="true"/>
  
</property>
 
</class>
</hibernate-mapping>

 


不用说,最顶层的hibernate-mapping节点是NHibernate用来进行映射的根了,其中,包含一个class节点,里面的name属性对应我们的Person类,注意,需要完整的限定名;而table属性,则显而易见是对应数据库中的Person表了。
我们再往里面看,分别有两个节点,一个是id,对应数据库中的id,一个是属性name,对应表中的column name和Person类中的name属性,整个映射文件简捷明了,一看即知。实际上这是由代码产生工具产生的映射文件,里面很多东西我们其实可以省略,如下写法:
<property name=”Name” column=”name” />
NHibernate将自动去匹配数据库中的列而不需要我们来设置。

下面,我们来看一下应用程序配置文件中都记录了那些东西,如下:
hibernate.connection.provider_class  
定制IConnectionProvider的类型.
例如:full.classname.of.ConnectionProvider (如果提供者创建在NHibernate中), 或者 full.classname.of.ConnectionProvider, assembly (如果使用一个自定义的IConnectionProvider接口的实现,它不属于NHibernate)。
 
hibernate.connection.driver_class  
定制IDriver的类型.
full.classname.of.Driver (如果驱动类创建在NHibernate中), 或者 full.classname.of.Driver, assembly (如果使用一个自定义IDriver接口的实现,它不属于NHibernate)。

hibernate.connection.connection_string  
用来获得连接的连接字符串.

hibernate.connection.isolation  
设置事务隔离级别. 请检查 System.Data.IsolationLevel 来得到取值的具体意义并且查看数据库文档以确保级别是被支持的。
例如: Chaos, ReadCommitted, ReadUncommitted, RepeatableRead, Serializable, Unspecified

hibernate.dialect 
NHibernate方言(Dialect)的类名 - 可以让NHibernate使用某些特定的数据库平台的特性
例如: full.classname.of.Dialect(如果方言创建在NHibernate中), 或者full.classname.of.Dialect, assembly (如果使用一个自定义的方言的实现,它不属于NHibernate)。

接着,我们开始解释代码的执行,如下:

 

 

 

Configuration config = new Configuration().AddAssembly("Test.Model");

//通过配置对象来产生一个SessionFactory对象,这是一个Session工厂,
//那么Session是用来干什么的呢?一个Session就是由NHibernate封装
//的工作单元,我们可以近似地认为它起到ADO.Net中Connection的作用。
ISessionFactory factory = config.BuildSessionFactory();
ISession session 
= factory.OpenSession();

Person person 
= new Person();
person.Name 
= "Jackie Chan";

//这里,开启一个由NHibernate封装的事务,当然,在这里最终代表
//的还是一个真实的数据库事务,但是我们已经不需要再区分到底是
//一个SqlTransaction还是一个ODBCTransaction了
ITransaction trans = session.BeginTransaction();
try
{
    
//保存,提交,就这么简单!!
         session.Save(person);
        trans.Commit();
        Console.WriteLine(
"Insert Success!");
}

catch (Exception ex)
{
        trans.Rollback();
        Console.WriteLine(ex.Message);
}

 


现在有了一个基本的概念了吧??
好了,第一篇就讲这么多,我们下次再接着练习。

Step by Step,顾名思义,是一步一步来的意思,整个教程我将贯彻这一理念,待此系列结束后,我们再就某些高级话题进行深入。

NHibernate Step by Step(二) 单表操作

接着第一期,我们继续。

为了方便学习测试,从今天开始我将使用MS Test来进行测试,这样就避免了在一个Console工程里不停地添加、注释代码了。

 

提示:为了在VS2005IDE中获得NHibernate配置文件的代码提示,请将你的$NHibernate\src\NHibernate下的nhibernate-configuration-2.0.xsdnhibernate-mapping-2.0.xsd拷贝到\Program Files\Microsoft Visual Studio 8\Xml\Schemas下,这样当你编辑配置文件或者映射文件时,你将得到完整的代码提示。
VS2003请拷贝到\Program Files\Microsoft Visual Studio .NET 2003\Common7\Packages\schemas\xml下。
 

NHibernat内部使用log4net来进行日志操作,今天我们将在配置文件中添加log4net的配置,这样我们在测试的时候将可以清楚地看到NHibernate是如何进行工作的。

应用配置文件修改如下:

 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<configSections>
    
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System,Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
    
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  
</configSections>

  
<nhibernate>
    
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />
    
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" />
    
<add key="hibernate.connection.connection_string" value="Server=localhost;Initial Catalog=NHibernate;Integrated Security=SSPI" />
    
<add key="hibernate.connection.isolation" value="ReadCommitted"/>
    
<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" />
    
<add key="show_sql" value="true" />
  
</nhibernate>

  
<log4net>
    
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
      
<layout type="log4net.Layout.PatternLayout">
        
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
      
</layout>
    
</appender>
    
<root>
      
<level value="ALL" />
      
<appender-ref ref="ConsoleAppender" />
    
</root>

  
</log4net>

</configuration>

 


请注意添加:

 

<add key="show_sql" value="true" />

 

 

 

关于log4net的使用,我们这里不做详细的讲解,有兴趣的请参考如下地址:

http://logging.apache.org/log4net/

 

接着,我们在上次的工程组中添加一个名为Test1的测试项目,将其中的不需要的手动测试去掉。请注意:除了NHibernate\Model引用外,还需要添加如下3个引用:

log4net,System.Data,System.Xml.


修改代码如下:

 

using System;
using System.Text;
using System.Collections;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NHibernate;
using NHibernate.Cfg;
using log4net;
using log4net.Config;
using Test.Model;

namespace Test1
{
    
/// <summary>
    
/// Summary description for UnitTest1
    
/// </summary>

    [TestClass]
    
public class UnitTest1
    
{
        
static ISessionFactory factory;
        
static ILog logger;
        ISession session;

        
public UnitTest1()
        
{
        }


        
Additional test attributes
    }

}

 

 

我们在测试的开始对Configuration\SessionFactory\Log进行初始化。在每一个Test的开始获取一个新的session,每一个Test结束后即关闭session


添加如下一个
Get测试:

 

[TestMethod]
public void TestRead()
{
      Person person 
= (Person)session.Get(typeof(Person), 1);
      Assert.IsTrue(person.Name 
== "Jackie Chan");
}

 


我们在前面曾经插入一条名为“Jackie Chan”的记录,现在在Test Manager中选中TestRead,运行,ok,Passed!
我们使用了session.Get来获取记录,方法如下:

object Get(Type clazz,object id);

很简单,一目了然。


我们切换到
Test Results窗口,双击测试成功的TestRead方法,这时将会有一个详细的测试结果显示出来,NHibernate将使用我们指定的log4net来输出详细信息,我们仔细观察:


2006-04-15 13:52:13,000 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - opened session

2006-04-15 13:52:13,015 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - loading [Person#1]

2006-04-15 13:52:13,015 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - attempting to resolve [Person#1]

2006-04-15 13:52:13,015 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - object not resolved in any cache [Test.Model.Person#1]

2006-04-15 13:52:13,015 [AdpaterExeMgrThread1] DEBUG NHibernate.Persister.EntityPersister [(null)] - Materializing entity: Test.Model.Person#1


2006-04-15 13:52:13,078 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.BatcherImpl [(null)] - Opened new IDbCommand, open IDbCommands :1

2006-04-15 13:52:13,078 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.BatcherImpl [(null)] - Building an IDbCommand object for the SqlString: SELECT person0_.id as id0_, person0_.name as name0_ FROM Person person0_ WHERE person0_.id=:id

2006-04-15 13:52:13,093 [AdpaterExeMgrThread1] DEBUG NHibernate.Type.Int32Type [(null)] - binding '1' to parameter: 0

2006-04-15 13:52:13,093 [AdpaterExeMgrThread1] INFO  NHibernate.Loader.Loader [(null)] - SELECT person0_.id as id0_, person0_.name as name0_ FROM Person person0_ WHERE person0_.id=@p0

2006-04-15 13:52:13,093 [AdpaterExeMgrThread1] DEBUG NHibernate.SQL [(null)] - SELECT person0_.id as id0_, person0_.name as name0_ FROM Person person0_ WHERE person0_.id=@p0

2006-04-15 13:52:13,093 [AdpaterExeMgrThread1] DEBUG NHibernate.SQL [(null)] - @p0 = '1'


2006-04-15 13:52:13,093 [AdpaterExeMgrThread1] DEBUG NHibernate.Connection.DriverConnectionProvider [(null)] - Obtaining IDbConnection from Driver

2006-04-15 13:52:13,859 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.BatcherImpl [(null)] - Opened Reader, open Readers :1

2006-04-15 13:52:13,859 [AdpaterExeMgrThread1] DEBUG NHibernate.Loader.Loader [(null)] - processing result set

2006-04-15 13:52:13,875 [AdpaterExeMgrThread1] DEBUG NHibernate.Loader.Loader [(null)] - result row: 1

2006-04-15 13:52:13,875 [AdpaterExeMgrThread1] DEBUG NHibernate.Loader.Loader [(null)] - Initializing object from DataReader: 1

2006-04-15 13:52:13,875 [AdpaterExeMgrThread1] DEBUG NHibernate.Loader.Loader [(null)] - Hydrating entity: Test.Model.Person#1

2006-04-15 13:52:13,906 [AdpaterExeMgrThread1] DEBUG NHibernate.Type.StringType [(null)] - returning 'Jackie Chan' as column: name0_

2006-04-15 13:52:13,906 [AdpaterExeMgrThread1] DEBUG NHibernate.Loader.Loader [(null)] - done processing result set (1 rows)

2006-04-15 13:52:13,937 [AdpaterExeMgrThread1] DEBUG NHibernate.Driver.NHybridDataReader [(null)] - running NHybridDataReader.Dispose()

2006-04-15 13:52:13,937 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.BatcherImpl [(null)] - Closed Reader, open Readers :0

2006-04-15 13:52:13,937 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.BatcherImpl [(null)] - Closed IDbCommand, open IDbCommands :0

2006-04-15 13:52:13,937 [AdpaterExeMgrThread1] DEBUG NHibernate.Loader.Loader [(null)] - total objects hydrated: 1

2006-04-15 13:52:13,953 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - resolving associations for: [Test.Model.Person#1]

2006-04-15 13:52:13,953 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - done materializing entity [Test.Model.Person#1]

2006-04-15 13:52:13,953 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - initializing non-lazy collections

2006-04-15 13:52:13,953 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - closing session

2006-04-15 13:52:13,953 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - disconnecting session

2006-04-15 13:52:13,953 [AdpaterExeMgrThread1] DEBUG NHibernate.Connection.ConnectionProvider [(null)] - Closing connection

2006-04-15 13:52:13,968 [AdpaterExeMgrThread1] DEBUG NHibernate.Impl.SessionImpl [(null)] - transaction completion

 

在其中,我们可以发现:


2006-04-15 13:52:13,093 [AdpaterExeMgrThread1] DEBUG NHibernate.SQL [(null)] - SELECT person0_.id as id0_, person0_.name as name0_ FROM Person person0_ WHERE person0_.id=@p0

2006-04-15 13:52:13,093 [AdpaterExeMgrThread1] DEBUG NHibernate.SQL [(null)] - @p0 = '1'


对了,就是这里,
NHibernate替我们构造了一条sql语句,并添加一个参数,然后将我们在代码中赋的id1来填充,这样,一条完整的可以执行的sql语句产生了。

请注意:在产生sql语句的前面,NHibernate构造了一个IDBCommand,然后在sql语句产生完全后,获取连接,通过一个DataReader来填充Persion对象给我们使用,这就是NHibernate替我们做的事,是不是很简单啊?(真的很简单吗??看看源代码吧!!)

请仔细研究输出的日志。

 

如法炮制,我们添加另外3Test,完成单个表的全部CRUD操作,如下完整代码:

 

[TestMethod]

        
public void TestCreate()

        
{

            Person person 
= new Person();

            person.Name 
= "Jackie Chan";

 

            ITransaction trans 
= session.BeginTransaction();

            
try

            
{

                session.Save(person);

                trans.Commit();

                Assert.IsTrue(person.Id 
> 0);

            }


            
catch (Exception ex)

            
{

                trans.Rollback();

                Assert.Fail(ex.Message);

            }


        }


 

        [TestMethod]

        
public void TestUpdate()

        
{

            Person person 
= (Person)session.Get(typeof(Person), 1);

            person.Name 
= "Jet Li";

 

            ITransaction trans 
= session.BeginTransaction();

            
try

            
{

                session.Save(person);

                trans.Commit();

                Assert.IsTrue(person.Name 
== "Jet Li");

            }


            
catch (Exception ex)

            
{

                trans.Rollback();

                Assert.Fail(ex.Message);

            }


        }


 

        [TestMethod]

        
public void TestRead()

        
{

            Person person 
= (Person)session.Get(typeof(Person), 1);

            Assert.IsTrue(person.Name 
== "Jackie Chan");

        }


 

        [TestMethod]

        
public void TestDelete()

        
{

            Person person 
= (Person)session.Get(typeof(Person), 1);

 

            ITransaction trans 
= session.BeginTransaction();

            
try

            
{

                session.Delete(person);

                trans.Commit();

            }


            
catch (Exception ex)

            
{

                trans.Rollback();

                Assert.Fail(ex.Message);

            }


        }


 

 

Delete的方法如下:

void Delete(object obj);

直接传入需要delete的对象即可。

 

好了,基本的操作都完成了,是不是很Easy?
好了,这一篇就讲这么多,我们下次再接着练习。

NHibernate Step by Step (三) Configuration和Sessionfactory

NHibernate Step by Step () ConfigurationSessionfactory

好了,我们再从头看一看Configuration,是否只能从App.config中取配置信息??

当然不是了,以下是3种最常见的配置:

<1> Configuration config = new Configuration();

这种配置方法将会到应用程序配置文件(App.Config,Web.Config)中查找NHibernate的配置信息,NHibernate的配置节必须符合应用程序配置文件个格式,前面的教程我们已经看到过了。

<2> Configuration config = new Configuration().Configure();

       这种配置方法将会在应用的相同目录查找名为”hibernate.cfg.xml”的标准Hibernate配置

       文件,格式如下:

        

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration  xmlns="urn:nhibernate-configuration-2.0" >

       
<session-factory name="MySessionFactory">

              
<!-- properties -->
              
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
              
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
              
<property name="connection.connection_string">Server=localhost;initial catalog=Hibernate;Integrated Security=SSPI</property>
              
<property name="show_sql">false</property>
              
<property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
              
<property name="use_outer_join">true</property>
              
<property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>

              
<!-- mapping files -->
              
<mapping assembly="Test.Model" />
       
</session-factory>
</hibernate-configuration>


       <3> Configuration config = new Configuration().Configure(configFileName);

       这种配置方法将查找指定的Hibernate标准配置文件,可以是绝对路径或者相对路径。

       另外我们还可以通过编码的方式添加配置信息:

       

Hashtable props = new Hashtable();
props[“dialect”] 
= xxxx;



config.Properties 
= props;


       这种方式不够配置文件来的灵活,所以我们一般不建议使用。

 

在取得config后,我们还需要进行映射文件添加,同样,我们常用以下3种不同的方法:

<1> config.AddXmlFile(“Person.hbm.xml”);

<2> config.AddClass(typeof(Test.Model.Person));

<3> config.AddAssembly(“Test.Model”);

以上方法都可以用“阶梯式“的编码风格,如下:

       

config.AddXmlFile(“Person.hbm.xml”)

      .AddXmlFile(“User.hbm.xml”)

      .AddClass(
typeof(Test.Model.Customer));

 

当然,为了方便起见,我们一般把所有的影射类文件及xml文件统一放在一个程序集中,然后使用config.AddAssembly(assemblyName)的方式,让NHibernate自动去查找指定程序集中所有的映射文件和映射类。

你有没有发现在我们使用标准的hibernate配置文件时,有如下一个元素:

<!-- mapping files -->

<mapping assembly="Test.Model" />

对了,你想的不错,我们可以避免在代码中捆绑映射程序集,而在配置文件中指定,这种方式可以说是最灵活的了。

提示:在Web程序中,最好将配置文件改为config的后缀,否则在没有添加对xmlasp.net映射的情况下,你的配置文件就有可能被下载哟!!

 

我们再来看看Sessionfactory

 

ISessionFactory sessionFactory = config.BuildSessionFactory();

 

在由Configuration构造完SessionFactory后,SessionFactory便保留了配置信息而不再需要Configuration,之后Configurationd的任何变化都不会影响到已经创建好的SessionFactory,如果你的配置信息发生变化,那么,毋庸置疑,你必须重新由Configuration构造出一个新的SessionFactory来。

注意:SessionFactory是一个Heavy Object,它不但保存了配置信息,而且还有所有的映射关系(你可以调用GetAllCollectionMetadata方法试验一下),以及需要进行总体维护的缓存等等信息,所以,我们不应该重复地创建SessionFactory对象,而应该充分共用已经创建出来的SessionFactory对象,同时,SessionFactory也是一个线程安全的对象,所以在多线程的场景下也完全可以共用。在我们实际开发中,我们可以在初始化的部分构造一个SessionFactory即可,比如,在一个公共静态类中初始化一个SessionFactoryreadonly常量等等。

 

另外需要注意的是,如果你要对多个数据库进行操作,那么你可以配置针对每个数据库而创建对应的SessionFactory——大部分情况下,一个数据库对应一个SessionFactory足够使用。

 

NHibernate Step by Step (四)Session、Query及HQL

NHibernate Step by Step (四)SessionQueryHQL


我们总结一下在第二篇中的
Session操作:

1.  获取记录

 

Person person = (Person)session.Get(typeof(Person),1);

 

 

2.  保存记录

 

session.Save(person);

 

 

3.  删除记录

 

Person person = (Person)session.Get(typeof(Person),1);

session.Delet(person);

 

 

我们再来看看其它几种方式:

1.  Load

 

Person person = (Person)session.Load(typeof(Person),1);

 

 

是不是感觉和Get一样?其实……很不一样!

Get在找不到对象时会返回一个null引用,而Load则会抛出一个ObjectNotFoundException,所以,不应该用Load来判断记录是否存在。另外还有两条非常重要的区别,我们将放在高级话题讨论。

2.  Query

 

IList list = session.CreateQuery(“from Person”).List();

Person person 
= (Person)list[0];

 

 

       ~!#@^?? from Person??#$@*&^$

       这是虾米??

       好象有点象SQL,不过少了个select啊!而且也没有表名啊!

       这就是久负盛名的HQL——Hibernate Query Language!

       HQL实际上是基于对象查询的一种仿SQL脚本,在运行期间,Hibernate会自动根据映射关系将之转换为真正的SQL语句来执行。

       什么?又一种SQL?不是说使用O/R Mapping就可以不写SQL了吗,怎么又多了一种SQL”??

       当然不是这样了,之所以产生HQL,就是为了能够更加灵活更加面向对象地操作数据,而且你不应该把HQL当成一种新的SQL,因
    为它——太简单好学了!

       看看下面的HQL,是不是很熟悉??

       

 

select p.Name from Person as p where p.Id=1
select Name from Person where Age>20
select Id,Name from Person where Id>1 and Age>20

 


       认出来老兄弟了吧!!

       (从这里开始,我们给person表添加了一个age列,请注意)

       ok,我们这里就详细解释。

       “from Person ”意思是取出Person类映射的person表中的所有记录,对应的SQL是:

       Select * from person”

       还可以加上别名:from Person (as) p = Select * from person (as) p

       这样一解释就很清楚了吧!一句话,你便已经掌握了HQL50%

       而“select Name from Person”,当然也就是只取person表的name列了!

       你所知道的where中的andorislikebetween><=等等都可以直接拿来使用,

       如下:

      

 

 select p.Name from Person p where p.Age>20

 
from Person p where p.Age between 20 and 30

 
from person p where p.Age in (20,30)

 
from person p where p.Name is null

 
from person p where p.Name like “Jaki%

 

      

       注意:字段大小写应该尽量跟属性名称保持一致,尤其是在使用别名的情况下,如:

       p.age将会导致一个“无法解析属性age“的错误。但是在不使用别名的情况下我的机器上”age“和’Age“一样可以通过,虽然如此,
    建议还是跟类文件保持一致。

 

执行HQL是通过IQuery来实现的,如下是详细的写法:

 

IQuery query = session.CreateQuery(“from Person”);

IList list 
= query.List();

 


   通过session来创建一个query,然后执行queryList方法,返回符合条件的IList

如上面的“from Person“,将返回person表的所有记录,list中将包含映射到Person

的对应表中所有记录的对象。

 

 

NHibernate Step by Step (五)Criteria Query

NHibernate Step by Step ()Criteria Query

可能很多人象我一样,刚开始接触HQL时,脑袋一片混沌,这是什么语法嘛!!之所以这样,是因为我们总是会先入为主地将之与SQL想比,虽然HQL看起来很SQL,而且设计时就有这样的意图,但是毕竟是两种差别很大的东西,难免就会出现理解偏差的问题。好了,我们今天就不让大家脑袋发晕了,HQL我们暂时放一放。今天我们来说另外一种查询方法:

Criteria Query.

什么是Criteria Query?简单说,就是将我们的查询条件封装为一个预定义的查询对象,由这个查询对象来执行查询,而不用我们再去写HQL了,而且更接近我们贯常的编程习惯。

是不是很不错?让我们来look look

 

 

//创建关联到某个类的查询对象
ICriteria criteria = session.CreateCriteria(typeof(Person));

//添加表达式
criteria.Add(Expression.Eq("Name","Jackie Chan"));

IList list 
= criteria.List();

 

 

注意这一句:

 

 

Expression.Eq("Name","Jackie Chan")

 

 

EqEqual的缩写,意思是添加一个查询表达式,Person.Name = “Jackie Chan”

对应HQL就是:

from Person p where p.Name=”Jackie Chan”

NHibernate会在运行时动态生成类似上面的HQL,我们可以在配置文件把show-sql打开,观看生成的SQL

这样是不是感觉清晰多了?又回到我们以前的编码习惯了!

 

为了对应HQL的种种查询条件,NHibernate预定义了大量的Expression方法,我们列几个如下:

 

 

Eq          = Equal
Gt          
= Greater than
Lt          
= Less than
Like        
= Like
Not         
= Not
IsNull      
= Is Null

 


基本上对应了大部分HQL的语义,详细的说明请参考api

 

下面,我们详细介绍Criteria的用法。

1.  Example查询

我们常常有这样的查询页面:

用户可以输入“姓名”、“性别”、“年龄”等等来进行查询,而我们常常的做法就是如下的烦琐:

 

string condition = “”;
if(txtName.Text != null)
        condition 
+= “ Name=” + txtName.Text;

if(txtSex.Text != null)
       condition 
+= “ and Sex=” + txtSex.Text;

……

 


代码看起来实在是不甚美观,有什么解决办法呢?

Criteria提供了专为这种问题而设计的Example查询,如下:


 

ICriteria criteria = session.CreateCriteria(typeof(Person));

Person person 
= new Person();
person.Name 
= "Jackie Chan";
person.Age 
= 50;

//创建一个Example对象
criteria.Add(Example.Create(person));
IList list 
= criteria.List();

 

 

请注意:

 

 

criteria.Add(Example.Create(person));

 

 

这句代码的意思是通过构造的person对象的属性来生成表达式,实际生成的代码如下:

 

 

SELECT this.id as id0_, this.name as name0_, this.age as age0_ FROM Person this WHERE (this.age = @p0)

 

 

对应上面的问题,我们简单地new出一个person对象,然后填充其属性即可,不用再去构造那丑陋的条件判断语句了!

 

2.  排序

我们想对返回的list进行排序,该怎么办呢?如下:

 

ICriteria criteria = session.CreateCriteria(typeof(Person));
criteria.Add(Expression.Gt(
"Age"20)); 

//添加一个排序对象
criteria.AddOrder(Order.Asc("Age"));

IList list 
= criteria.List();

 

 

请注意:

 

 

criteria.AddOrder(Order.Asc("Age"));

 

 

这句代码的意思是在criteria上构造一个排序对象,并以Age属性做正序排列,NHibernate在运行时会生成如下语句:

 

 

SELECT this.id as id0_, this.name as name0_, this.age as age0_ FROM Person this WHERE this.age > @p0 ORDER BY this.age asc

 

 

如你所猜想,Order类肯定有另外一个“Desc“方法:)

 

3.  限制记录范围

在显示大量的记录时,我们常常采用的方法就是分页,如果用NHibernate来做,该怎么办呢?

如下代码:

 

ICriteria criteria = session.CreateCriteria(typeof(Person));

//从第10条记录开始取
criteria.SetFirstResult(10);

//取20条记录
criteria.SetMaxResults(20);

IList list 
= criteria.List();

 

 

这样,我们就达到了分页的目的。

注意:

NHibernate的分页机制实际上依赖于不同的数据库实现,所以,对特定的某种数据库,并不一定是效率最好的,比如对SQLServer(为什么受伤的总是俺?为什么总是说俺比不上Oracle?俺都赶在2005年年底出2005版本了!!)。想知道为什么吗?很简单,check一下上面代码生成的SQL就清楚了!或者深入点再看看NHibernate的分页代码,我就不解释了,自己动手,丰衣足食:)

 

 

总体来讲,Criteria对我们来说更熟悉,更容易上手,但是目前Criteria还是不够完善——将对应的HQL一一封装实在太烦琐了,所以NHibernate还是以HQL查询为主,我们在使用的时候则看需要了,要么使用HQL,要么HQLCriteria混合使用,重要的是解决问题,对不?

posted on 2008-10-08 14:48  youngtsinghua  阅读(309)  评论(0编辑  收藏  举报