NBearV3 Step by Step教程——ORM进阶篇
版本
1.2 [2006-11-12]
简介
本教程在前一篇《NBearV3 Step by Step教程——ORM篇》的基础上,演示前文中没有涉及的实体自关联关系;及和性能相关的Attribute的设置:IndexProperty,BatchUpdate;并将详细比较和讨论NBear.Data.Gateway类中的强类型查询方法的使用和注意事项,特别是在不同的Attribute设置选项下的性能差异分析。
注:在阅读本文之前,建议读者先阅读《NBearV3 Step by Step教程——ORM篇》以掌握NBearV3中有关ORM的基本知识。
目标
通过本教程,读者应能够更全面地掌握使用NBearV3的ORM模块进行应用程序设计的过程,了解实体设计中与性能相关的重要选项,并能全面掌握NBear.Data.Gateway中各种强类型查询方法的使用。
代码
本教程演示创建的所有工程和代码,包含于可以从sf.net下载的NBearV3最新源码zip包中的tutorials\ORM_Adv_Tutorial目录中。因此,在使用本教程的过程中如有任何疑问,可以直接参考这些代码。
时间
<30分钟。
正文
Step 1 下载NBearV3最新版本及准备
1.1访问http://sf.net/projects/nbear,下载NBearV3的最新版本到本地目录。
1.2 将下载的zip文件解压至C:\,您将看到,加压后的NBearV3目录中包括:dist、doc、cases、src、tutorials等目录。其中,在本教程中将会使用的是dist目录中的所有release编译版本的dll和exe和tutorials目录中之前的ORM基础教程。
1.3 将tutorials目录中的整个ORM_Tutorial目录复制到任意其它位置,并命名为ORM_Adv_Tutorial,我们将以ORM_Tutorial为基础,演示NBearV3中的ORM的进阶知识。
Step 2 扩展设计实体及元数据
2.1 将ORM_Adv_Tutorial中的ORM_Tutorial.sln重命名为ORM_Adv_Tutorial.sln,并在VS2005开发环境中打开。
2.2 在EntityDesigns工程的Group.cs文件中,添加ParentID属性,并为ParentID属性添加IndexPropertyAttribute设置如下:
Guid ParentID
{
get;
set;
}
将属性标记为IndexProperty,表示在NBear.Tools.EntityDesignToEntity.exe生成的数据库创建脚本中,将自动为该属性创建一个单列索引。我们都理解数据库索引的作用——这将有助于改善以该属性为条件的查询性能。
2.3 还是在Group.cs文件中,我们为Group新增一个SubGroups属性如下:
Group[] SubGroups
{
get;
set;
}
我们可以看到,SubGroups属性的设置方式和普通的1对多关联的属性设置方式没什么两样,但是,可以注意到,它其实是Group实体1对多关联到Group实体自己,也就是说,它是一个1对多的自关联。
注:对于1对1自关联,也可以以类似的方式设置。但是,NBearV3还不支持多对多的自关联,也不支持一个继承体系中的类的多对多关联,例如,不支持User和LocalUser的多对多关联。
2.4 在User.cs文件和LocalUser.cs中,为User和LocalUser实体添加一个BatchUpdateAttribute如下:
public interface User : Entity
{
\\
}
[BatchUpdate(10)]
public interface LocalUser : User
{
\\
}
BatchUpdate这个Attribute对于该实体的查询性能将有重要影响。当使用BatchUpdate标记一个实体时,实体的Save和Delete级联操作,将在内部自动使用一个BatchGateway,以批更新方式执行。BatchUpdate的构造参数代表每多少个作为一个batch,连接数据库,并执行。举例来说,假如原来,User实体的一个实例Delete的时候,由于User实体自己,及所有User中以Contained这个Attribute标记的属性对象的数量一共有20个应该被自动Delete掉,那么,要连接20次数据库,执行20次Delete;而在设置了BatchUpdate(10)的情况下,因为每十个查询才连接一次数据库,所以,实际上,只需要连接两次数据库,每次分别发送并执行10个Delete操作。很显然,BatchUpdate对性能的改善是惊人的。
Step 3 从实体设计代码生成实体代码、实体配置文件和数据库生成脚本
3.1 至此,所有的实体的设计就扩展完毕了。编译EntityDesigns工程。
3.2 运行dist目录中的NBear.Tools.EntityDesignToEntity.exe工具,载入EntityDesigns工程编译生成的EntityDesigns.dll。
3.3 点击Generate Entities按钮,将生成的代码保存到Entities工程中的一个名叫Entities.cs的新代码文件。
3.4 点击Generate Configuration按钮,将生成的代码保存到website工程下的名为EntityConfig.xml的文件中。
3.5 点击Generate DB Script按钮,将生成的代码保存到website工程下的名为db.sql的文件,可以在某个新建的SQL Server数据库中执行这些脚本,创建对应于所有实体的数据库脚本。对于本教程而言,建议使用SQL Server 2000的查询分析器,在tempdb数据库中执行db.sql中的脚本。
Step 4 使用Gateway进行查询
4.1删除website工程中的Default.aspx.cs文件中原来的代码,参考tutorials\ ORM_Adv_Tutorial\website目录下的Default.aspx和Default.aspx.cs,创建Default.aspx。这些代码演示了Gateway类的大多数强类型查询方法的使用。关于Gateway支持的更多方法的介绍,可以参考doc目录下的SDK类库文档。
4.2 运行以上代码,在输出文本框内,您将得到详细的SQL调试信息及相关说明。
在运行结果中,大家可以注意比较以“TEXT 内容”格式出现的SQL日志,这些日志显示了何时,NBearV3的强类型查询在内部执行了什么样的SQL查询。
有两个地方需要特别留意比较的,一个是BatchUpdate后非BatchUpdate方式下的Save和Delete时,实际执行的SQL语句;另一个是,注意LazyLoad=true和LazyLoad=false的属性,载入的时机的不同。
正文结束。
附录
1 关于Entity.Attach()/Detach()/IsAttached()方法的使用
细心的读者会发现,NBearV3中,保存实体只有Save方法,而不区分Insert还是Update。那么,是如何区分一个实体什么时候该Insert,什么时候该Update的呢?
NBear判断的是Entity.IsAttached()方法。一个刚刚new出来的实体实例,总是IsAttached() == false的,因此,当Save它时,实际会执行Insert,而一个通过Find返回的实体实例,则总是IsAttached() = true的,当保存这样的实体时,只有修改过的属性会被Update。
理解以上这一点有什么作用呢?在需要的情况下,我们可以灵活地直接调用Entity.Attach()/Detach()来修改IsAttached()的值,从而实现一些特殊需求的查询。
例如:
假如我们想更新User对象的Status,而我们的输入参数只有User的id。如果不了解Attach()/Detach()方法,那么,只有先通过Gateway.Find方法找到ID=id的User实例,修改Status,再Save它。这至少需要两次数据库查询,一次Select,一次Update。更有效的做法是,我们可以new一个User的实例user,手动设置这个user.Attach(),修改user的Status,再Save它。这样,这个user被保存时,也只会Update ID为id的User的Status属性,但是,此时,只需要一次数据库查询,就是Update。
相反的,Detach()方法,可以设置一个Attached的实体实例的IsAttached() = =false。这有什么用呢?
例如:
我们已经通过某些渠道得到了一个User的实例user,在某个算法中,我们可能要频繁的写User的属性,最后,才会保存这个user。如果不设置Detach(),则实体属性的每次修改都会触发Entity.OnPropertyChanged事件,这不仅意味着性能损失,也意味着,Entity.OnPropertyChanged的订阅者可能要响应很多不必要的事件变更事件。此时,理想的方法是,在频繁的写User的属性的算法执行之前,我们先Detach(),算法完成后,我们再Attach()。
2 Gateway.RegisterSqlLogger()/UnregisterSqlLogger()
您应该已经注意到,Default.aspx.cs文件中的Page_Load中Gateway.RegisterSqlLogger()的使用:
gateway.RegisterSqlLogger(new LogHandler(WriteLine));
对应于注册一个SqlLogger,我们也可以调用UnregisterSqlLogger()来注销一个SqlLogger。
这两个方法对于调试应用程序的运行和程序性能的优化非常重要,建议大家在开发中一定要注意使用,从而有效监视NBear提供的强类型外衣下的,真正的程序和数据库的交互细节。
//本文结束