请注明转载地址:http://www.cnblogs.com/arhat
通过上一章的学习,我们学会如何使用NHibernate对数据的简单查询,删除,更新和插入,那么如果说仅仅是这样的话,那么NHibenrate的优势有在哪里呢?那么今天就要和大家一起来分享一下NHibernate的优势——懒加载(初探)。
我们知道,在关系数据库中,表和表之间是有联系的,那么通常情况下,我们通过连接查询能够把相关的数据查询处理,只是连接语句似乎大概写起来似乎是有些繁琐的,那么NHibenrate为我们提供了一种变相的操作,可以使这种连接查询变得简单多了。
现在呢,我们更改一下数据库,在数据库中我们新建一个表Clazz(班级表),并插入相关的数据。
同时,我们得更改一下Student表,在Student表中加入一个外键Cid,我们知道,学生和班级之间是存在者关系的。我们从两个方面来说:
对于Clazz:一个班级对应多个属性就是一对多的关系(one-to-many),是one的一方
对于Student:多个学生对应一个班级就是多对一的关系(mand-to-one),是many的一方
所以,我们需要对Student表中建立一个和Clazz对应的外键
好,现在我们清空一下Student中数据。
下面就是一个重点了,我们需要改写一下Student实体类和添加一个Clazz实体类。由于Student是Clazz的外键表,所以,我们应该这样改写Student实体类,在Student实体类中添加一个属性为Clazz,用来获得这个学生对应的班级,代码如下:
public class Student { public virtual int SId { get; set; } public virtual string SName { get; set; } public virtual string SSex { get;set; } public virtual DateTime SBirthday{get;set;} public virtual Clazz Clazz { get; set; } }
然后添加Clazz类,代码如下
public class Clazz { public virtual int CId { get; set; } public virtual string CName { get; set; } public virtual ISet<Model.Student> Students { get; set; } }
由于,Clazz和Student是多对一的关系,所以在Clazz中需要定义一个集合属性,用来获得这个班级中的学生。这里使用的集合是Iesi.Collections.Generic.ISet类型。那么需要在Model项目中添加Iesi.Collections.dll的引用。
但是我们虽然更改了实体类的属性,但是NHibernate却不知道他们之间的关系,所以我们需要更改Student.hbm.xml和Clazz.hbm.xml映射文件,使NHibernate能够知道他们之间的关系。
Student.hbm.xml内容如下:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping assembly="Model" namespace="Model" xmlns="urn:nhibernate-mapping-2.2"> <class name="Student" table="Student"> <id name="SId" type="int"> <column name="sid"></column> <generator class="native"></generator> </id> <property name="SName" type="string"> <column name="sname"/> </property> <property name="SSex" type="string"> <column name="ssex"/> </property> <property name="SBirthday" type="DateTime"> <column name="sbirthday"/> </property> <many-to-one name="Clazz" column="CId" class="Model.Clazz"></many-to-one> </class> </hibernate-mapping>
在Student.hbm.xml中,我们添加了一个<many-to-one>的节点,这个节点是用来说明在Student中Clazz属性是一个外键。其中name是Student中Clazz属性的属性名,column是Student表中的外键字段名,class是用来设置和Student关联的对象的完整名字(命名空间+类名)。
然后,我们看一下Clazz.hbm.xml的文件内容。
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping assembly="Model" namespace="Model" xmlns="urn:nhibernate-mapping-2.2"> <class name="Clazz" table="clazz" lazy="true"> <id name="CId" type="int"> <column name="cid"></column> <generator class="native"></generator> </id> <property name="CName" type="string"> <column name="cname"></column> </property> <set name="Students" table="student"> <key column="Cid"></key> <one-to-many class="Model.Student"/> </set> </class> </hibernate-mapping>
大家可以看到,由于在Clazz实体类中我们定义了一个集合属性,那么也就是表示了Clazz和Sutdent之间的多对一关系。那么自在Clazz.hbm.xml映射文件中,需要通过<set>节点来声明这个属性。其中<Set>节点中的name是Clazz中的集合属性名,table是指对应着数据库的那个表。其中<set>节点中有两个子节点<key>是用来说明Student表中的外键字段,<ono-to-many>是用来说明班级和学生之间的一对多关系,class属性是指和Clazz关联的Student的全类名。
一旦这两个映射文件通过<many-to-one>和<one-to-many>的设置,那么NHibenrate就知道了Student和Clazz之间的关系了。
由于在Student表中没有数据,我们现在插入几条数据来做测试。
然后,我们得做两个测试,才能说明NHibernate给我提供的懒加载机制以及测试中的问题。现在我们更改一下D_User.cs的代码
public Model.Student GetUser(int id) { //using(ISession session = NHibernateHelper.OpenSession()) //{ // return session.Get<Model.Student>(id); //} //} ISession session = NHibernateHelper.OpenSession(); { return session.Get<Model.Student>(id); } }
现在老魏有一个要求,就是要查找一下id=1学生的姓名和所在的班级名称。那么如果在SQL中,我们得使用一个连接语句,但是在NHibernate中一切将会变得简单多了。
然后,我们在主程序中更改一下代码:
DAL.D_User dal = new DAL.D_User(); Model.Student student = dal.GetUser(1); Console.WriteLine("学生:"+student.SName+",所在的班级是:"+student.Clazz.CName);
运行一下,看看结果如何
我们发现,的确查询出了正确的结果。那么在执行的时候,NHibernate发出了两条SQL语句,那么大家可以看出,一个是查询Student的语句,一个是查询班级的SQL语句,那么现在我们的问题就来了,为什么NHibernate会发出两条语句呢。我们来测试一下,我们把:
Console.WriteLine("学生:"+student.SName+",所在的班级是:"+student.Clazz.CName);
给更改一下,只输出学生的姓名。我们会发现NHibernate只发出了一条SQL语句。
那么这是为什么呢?原来是NHibernate在做查询的时候,只是把数据Student表的数据查询出来了,反而和它关联的Clazz数据并没有查询出来。但是如果我们把注释去掉,在运行的时候,会发现NHibernate发出两条语句,而第二条语句就是用来查询和Student关联的Clazz属性的。这是为什么呢?这个原因就是在NHibernate默认情况下是启用懒加载机制的,那么什么是懒加载呢?
懒加载,我们认为是在需要的时候才开始执行,不需要的时候就不执行。那上面我们的测试结果已经说明这个问题了,当我们只是查询Student的基本信息时,它就只查询Student信息,但是现在由于我们不紧要显示Student的基本信息,还要显示班级信息,那么在显示完Student信息之后,发现有一句话:student.Clazz.CName。那么NHibenrate就会知道:”哦,现在你需要你的班级信息了,那好吧,我把班级信息查询出来,并班Clazz的信息创建一个对象,把这个对象赋值给Student的Clazz属性吧”。此时,NHibenrate就会向数据库发送一条SQL语句来查询Clazz的信息。
从上面我么可以看出,NHinberate的最大优点就是拥有的懒加载,使我们的查询变得简单起来了。
下面我们得做第二实验,D_User代码如下:
public Model.Student GetUser(int id) { using(ISession session = NHibernateHelper.OpenSession()) { return session.Get<Model.Student>(id); } } //ISession session = NHibernateHelper.OpenSession(); //{ // return session.Get<Model.Student>(id); //} }
这回呢,我们使用using语句来释放ISession资源。主程序代码不变,我们来看看运行的结果:
出错了,报了一个异常”no Session”。这是为什么呢?因为我们使用using语句来强制释放ISession的资源,而这个时候只能查询出Student的基本信息,反而查询不到了和它关联的Clazz西信息,原因很简单,就是懒加载的执行需要ISession在没有释放的前提下才能够执行的,所以我们一旦释放了ISession的资源,则懒加载是不起作用的。那么看到这里,大家可以想到,这是懒加载的问题,那么如果我们不使用懒加载不就可以了吗?我们来做一下实验。把Student.hbm.xml和Clazz.hbm.xml的<class>节点中取消懒加载,代码如下:
<class name="Clazz" table="clazz" lazy="false"> <class name="Student" table="Student" lazy="false">
然后我们运行一下程序,看看结果如何:
的确查询出来了,但是我么看看SQL语句,此时的SQL语句是一个连接查询,当然从性能上看基本上没有什么影响,因为这是从many这一段发起的。如果从one那一短发起,那就大大不同了,至于为什么,我们在下一章中来讨论。
我们会发现,如果我们取消了懒加载,那么结果是正确的,这就叫“立即执行”。但是,如果没有了懒加载的话,那么我们在写程序的时候会非常的难受,因为我们感受到懒加载给我们带来的好处,一般情况下,我们在使用完ISession之后要释放资源的。所以这里就出现一个矛盾,我们既要释放资源,也要使用懒加载,那么该怎么办呢?请看下章!