代码改变世界

NH in Action 2.1 study log

2009-12-08 10:17  jiva  阅读(291)  评论(0编辑  收藏  举报

2.1节

This chapter covers

    • Ø "Hello World "。World越来越像个大头头。谁出来都要拜一下码头,打声招呼。按lilei和HanMeiMei的标准应该是how do you do 么。
    • Ø 如何架构NHibernate Application
    • Ø 编写并映射一个Simple Entity
    • Ø 配置NHibernate
    • Ø 实现初级的(primitive)的CRUD操作。我喜欢primitive这个词。在看过的一些BLOG, BBS中总有人拿着实现了NHibernate的CRUD操作的Demo来炫耀,看了觉得NHibernate比ADO操作麻烦多了。。;我想知道NHibernate到底能干什么,最适合干什么。

Steps: 记录一下:

  • Ø Set up the Project

(一)添加引用。在Project中添加Reference: NHibernate.dll

(二)相应单元中 Using NHibernate, NHibernate.Cfg。

  • Ø Create Employee Class 创建要持久化的Class,有时候他们老叫做Entity Class。这次设计的类没有一如既往的毫无新意,使用几个Integer和String代替, 呵呵,加了个自我引用。I Like Tree! 原文直接使用了字段,我们还是遵循good Practice的,改成Property。

public class Employee
    {
        
public virtual int ID { getset; }
        
public virtual string Name { getset; }
        
public virtual Employee Manager { getset; }
    }

 

文中提到这样的一句话: If two instances of Employee have the same identifier value, they represent the same row in the database。也就是说如果两个对象具有同样的识别符,那么他们代表数据库中同一行。

留个疑问:

  • ² 在需要频繁对比是否同一记录的Process中, 这样是不是意味着需要重写Equals方法?由于多数判断是否同一记录的基本上都是用主键,NH是否存在较为快捷高效的方法来进行对比
  • A: 对于存在唯一主键的记录中,只需要根据主键进行对比即可,如果不能根据唯一的主键区分开始究竟是不是同一条记录,那么只能重写Equals方法了。对于一个系统来说,上百上千的对象如果都需要Override这个Equals方法,的确有点麻烦。不过想想目前的系统中,基本上都是使用ID来进行判断的。所以只需要重写基类的即可。

关于NH是否存在对比对象的方法,猜猜以为应该是在Session中的,发现没有。保留疑问吧。

  • ² 如果重写Equals是否会对NHibernate产生side effect?
  • A: 既然NH是根据RowID来的,那就是没有什么影响,肯定是走自己的一套了。这个会对Collection有影响,但这不是NH的范围了。
  • ² 如果使用同一ID去获取对象,这两个对象时Reference Equal么?如果不是,NH就可能每次都去数据库取一次了,如果是,比如存在缓存。改变一个Object的ID会产生什么影响?
  • A:先创建一个对象,然后再获取一次,NH会返回两个不同的对象。但是如果使用某个ID取获取对象的时候,如果这个对象之前获取过,那么通过显示的NH的SQL和SQL Server的Profiler发现,NH并没有再执行一次数据库查询,估计这个和缓存有关,猜NH缓存的是DataRow,而不是Object。

在一个Session A中获取ID为2的某个对象,然后单独开另外一个Session B,获取ID为2的对象,然后修改Name属性,保存提交。再在Session A中获取ID为2的对象,依然是没有更新的对象。但是如果两个对象都是同一个Session中获取的话,那么当修改对象2的属性的时候,对象1的属性也随之更新了。很怪呀。有点像是GSP的View。猜缓存还可能是Session级别的?

如果改变Object的ID的话,NH会抛出来一个异常,说明ID被修改了。会和,还不如将ID就定义为ReadOnly的呢。公司的GSP数据库中是每次都获取一个IGSPRecord interface, 并且iRecord.ID不可修改,但是可以在Load的状态修改。这点和NH还是有点不同的。

作者说"Instances of the Employee class may be managed(made persistent) by NHibernate", 呵呵,用了个May Be。

作者这里提到了NH的一个Important Feature: The persistent class can be used with or without NH --no special requirements are needed. 想想这的确算是一个很好的特性。多数据库支持应该不算什么突出的特性,通过一定的抽象手段,架构于ADO或者ADO.net任何Framework都可完美支持,在领域类绝对独立方面,个人觉得这也是表模块模式不如领域模式的一个地方,毕竟使用TDataSet写的业务逻辑挪到GSPTable或者使用ADOTable都是一个翻译重写的过程。

  • Ø Setting up the database 作者这里使用SQL创建的Database和Table Schema。既然NH有生成Schema的工具,干吗用这个。作者说要在第九章讲。
  • Ø Creating an Employee and saving to the database。这个过程和your first application base on NH 差不多。创建一个单例的SessinFactory,获取Session,然后将New出来的Employee通过调用Session的Save保存。

代码
 1   
 2  public class EmployeeReporsitory
 3     {
 4         private static ISessionFactory _sessionFactory;
 5 
 6         public ISessionFactory SessionFactory
 7         {
 8             get {
 9                 if (_sessionFactory = null)
10                 { 
11                     Configuration cfg = new Configuration();
12                     cfg.AddAssembly(Assembly.GetCallingAssembly());
13                     cfg.Configure();
14                     _sessionFactory = cfg.BuildSessionFactory();
15                 }
16                 return _sessionFactory;
17             }
18         }
19 
20         public void DoSaveEmployee(Employee employee)
21         { 
22             using(ISession session = SessionFactory.OpenSession())
23             {
24                 using (ITransaction tran = session.BeginTransaction())
25                 {
26                     session.Save(employee);
27                     Tran.commit();
28                 }
29             }
30         }
31 
32

 

留个疑问:

  • ² 如果传过来的Object为NULL, NHibernate会怎么出来?报错么?我觉得会报错。处理方式有两种么:跳过,或者报错。报错能让我知道结果。
  • A:报错. attempt to create saveOrUpdate event with null entity。
  • ² ID如果是数据库生成的,那么会存在ID冲突的问题么?Save的过程Get的还是Trans.Commit得过程Get的?
  • A:开始以为必须调用Transaction.commit才会执行SQL,没有想到调用Trans.Begin的时候已经开始开始了数据库连接事务,不调用Trans.commit也能提交修改,使用时SQLServer的默认事务。

作者说“Don't use this OpenSession function in your production projects。You'll learn more economical approaches throught out this book”. I Expect.

  • Ø Loading an Employee from the database

从数据库中获取保存的Object。作者使用的是HSQL,和SQL 没有什么两样么。编译时都不能检查出来错误,有啥优势。Try 。

代码
     public IList<Employee> GetEmployees() 
        {
            
using (ISession session = SessionFactory.OpenSession())
            { 
                IList
<Employee> employes = session.CreateQuery("from Employee as emp order by emp.name asc").List<Employee>();
                
return employes;
            }
        }

 

  • Ø Creating a mapping file

      在这一节里,作者简要描述为什么要MappingFiles:“ It First need more information about how the Employee class should by made persistent"。也就是说在Object和Database之间存在一定的映射关系,才能根据这个映射关系将Object 持久化。

想想,DataSet也是存在这样的映射关系,只不过是由于DataSet的设计和Database很相像,自身包含了映射到数据库的表、列等信息。公司的GSP也是差不多,获取一个Record,也能通过Record获取到其Owner(表)的Schema信息。

    依稀记得谁说过在设计一个数据结构或者类的时候,最好要有self-dected的信息,不过像这种DataSet 和Database的关联关系就比较麻烦了。如果都采用*还可以,如果通过具体的列名来指定映射,一旦Database Schema修改了,那么其后的一切都是要修改的。从目前对NHibernate了解来看,Database Schema修改后同样需要修改Mapping Files,不过从另一个角度来说,如果所有的一切都是以Object为中心,那么当Object某个属性修改后,首先在Class的层面可以通过工具都修改完毕,编译通过应该就没有什么事情了,然后根据Mapping Files生成相应的数据库。这貌似也是NHibernate的一个优点。

画个图,理一下。

clip_image001

Database Schema修改影响分析:

1. 获取数据的过程。 如果获取数据的时候没有采用*,而是采用了具体的字段名,那么修改Schema必然导致对获取数据的表映射或者SQL或者视图或者存储过程等产生影响。而采用*的坏处就是存在效率问题,这在SQL中是不推荐使用的。另外还有一点,即便采用了*,如果有些新增字段或者其他表中计算字段依赖与*中的某些字段,这些地方也可能受到影响。

2. 处理数据的过程。 这个过程包含两部分:第一部分是获取数据后对数据进行处理,第二部分是保存数据时对数据进行处理。实现的方式有可能是通过服务器端代码来实现,也有可能借助数据库的存储过程或函数来实现。

a) 获取数据后对数据处理。比如将数据展现为用户希望的数据,求合计值, 翻译枚举值,根据业务逻辑处理数据(比如根据根据一堆的单据计算费用列表),这些过程中都存在着对某个明确字段的处理。这时候database schema 修改会对这一过程产生影响的。

b) 保存数据的时候对数据处理。这个过程主要是对用户输入的数据进行校验和处理。比如有效性校验,唯一性校验,业务算法处理,生成冗余表等。这个过程基本上没有通过*来处理数据的吧。所以这个过程受到 database Schema的影响是最多的。

3. 展现数据的过程。 这个过程中使用*的是有并不多见,基本上都是明确的字段。主要是因为根据用户的要求,字段存在展现顺序,字段有独特的展现方式,有些对字段只读、编辑方式的规则等(以公司的GSF为例),像这些地方都是以具体的列名呈现的。数据库的Database Schema修改对这里影响最大。当然这些变化可以隔离到获取数据的过程中,如果这样的话,就不能在获取数据的过程中使用*,因为获取数据的过程存在的一个任务就是要对界面呈现的字段和数据库的字段进行映射。界面处理比较麻烦的还有一个地方就是同一份数据可能在多个地方展现,比如说弹出窗体,列表,报表。从工作量上来说修改Database Schema,界面修改的工作量最大了。此时最好的做法就是界面的设置要文本化,可以批量替换,然后冒烟即可。

综上所述:当database schema修改的时候,在一个以Database为中心的Application中,获取数据、处理数据、展现数据的过程都会受到影响。其中展现数据和处理数据的过程受到的影响最大。

Ps: 判断一个Application是否以Database为中心的最好的方法就是:是不是经常说或者认为:“XX,数据库都设计完了,可以做了”

² 留个疑问: 是否存在比较好的方式解决在以Database为中心的Application中由于Schema修改造成的影响?

A: 未解决。

如果是采用Object的方式的

1.获取数据的过程:如果是根据ID获取某个对象,那么当Domain Class修改的话,编译即可发现问题,如果是HSQL或者criteria方式或者SQL,这个过程中由于这些属性都是通过字符串形式展现的,比如上面的HSQL查询例子中,那么像这些地方只有运行时才能发现错误,编译时发现不了错误,Domain Class修改的还是会产生影响。想想,这个问题或许可以通过单元测试来解决。

2.处理数据的过程。 这个过程由于都是通过Object来进行处理的,所以基本上不受到影响。当然要保证获取的数据时正确的。

3.展现数据的过程。这个过程不了解,暂时留个疑问。

当然,上述假设的前提是可以根据Object Mapping Files来生成Database,在这种情况中,基本上不需要在Mapping Files中指定列名。如果指定了列名,那么可能修改的只有Mapping Files一处地方,比较集中。这也是比较好的事情。

留个疑问:

² NHibernate中如何解决字符串形式的那些查询?对于统计分析类模块中的大量的SQL如何替代?

A:HSQL中的对象和映射文件中的对象要求都要和类名,属性名匹配,区分大小写,而且只能在运行时发现。在区分大小写上还不如SQL呢。

² 基于NHibernate的Application的展现部分有啥特殊之处?如果Database Schema或者Domain Class修改的话,是否会受到影响?

A: 未解决。

² 还有一种情况就是记录的修改对于Object或者Database Schema产生的影响有多大?比如说枚举值的开始值发生变化了。

A: 未解决。

综上:在一个应用程序中,数据库修改多是增删操作,而修改某个字段的名字的情况则比较少见。不过对于增删字段或者表的操作,如果能够在编译时就能发现或改正错误总比在运行时报出来好一些。运行时发现的错误多数都是地雷阵,有时候需要一个工兵集团军才能排除彻底。

回归主题: 作者创建的XML Mapping 大体如下所示:

代码
<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
    auto
-import="true">
  
<class name="HelloNHibernate.Employee, HelloNHibernate" lazy="false">
    
<id name="id" access="field">
      
<generator class="native" />
    
</id>   
    
<property name="name" access="field" column="name"/>
    
<many-to-one access="field" name="manager" column="manager" 
        cascade
="all"/>
  
</class>
</hibernate-mapping>

 

作者在这里指定了列名,其实从Your First Application Base on NHibernate 上我们知道这是没有必要的。

作者接下来解释了每个节点的含义。比如说Class节点表示映射到表Employee,也就是类名。Manger属性是个一对多的关系,映射到列MangerID上,估计作者上面写错了,写成了Manger。

  • Ø Configuring your application

配置NH的过程和在app.config或者web.config中使用ConnectionString是类似的。作者在这里选择了app.config方式。具体的配置过程如下:

1. 添加一个Application Configuration File: app.config

2. 内容大体如下:

代码
  <?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<configSections>
    
<section name="nhibernate" 
             type
="System.Configuration.NameValueSectionHandler, 
             System, Version=1.0.3300.0,Culture=neutral, 
             PublicKeyToken
=b77a5c561934e089" 
             />
  
</configSections>
  
<nhibernate>   
    
<add key="hibernate.show_sql" 
         value
="false" />
    
<add key="hibernate.connection.provider" 
         value
="NHibernate.Connection.DriverConnectionProvider" />
    
<add key="hibernate.dialect" 
         value
="NHibernate.Dialect.MsSql2000Dialect" />
    
<add key="hibernate.connection.driver_class" 
         value
="NHibernate.Driver.SqlClientDriver" />
    
<add key="hibernate.connection.connection_string" 
         value
="Data Source=127.0.0.1; 
  Database=HelloNHibernate;Integrated Security=SSPI;" />
  </nhibernate>
</configuration>

 

个人觉得这种方式不如使用hibernate.cfg.xml来的方便。

  • Ø Updating an Employee

接下来需要实现的是更新一个Object。作者的代码大体如下:

代码
static void UpdateTobinAndAssignPierreHenriAsManager()
{
    
using (ISession session = OpenSession())
    {
        
using (ITransaction transaction = session.BeginTransaction())
        {
            IQuery q 
= session.CreateQuery(
"from Employee where name = 'Tobin Harris'");
     Employee tobin 
= q.List<Employee>()[0];
            tobin.name 
= "Tobin David Harris";
            Employee pierreHenri 
= new Employee();
            pierreHenri.name 
= "Pierre Henri Kuate";
            tobin.manager 
= pierreHenri;
                        transaction.Commit();
            Console.WriteLine(
"Updated Tobin and added Pierre Henri");
        }
    }
}

 

大体过程是从数据库中获取一个数据,然后更新某个字段值,调用Transaction.Commit, 这个过程和之前学习的Your First Application base on NH不大一样。呵呵。没有调用Session的Update方法。

留个疑问:

² 如果一个对象是从Session中获取的,那么不调用相应的Update是不是就可以保存到数据库中?如果不是从Session中获取的但是已经存在的呢?

作者展现NH生成的SQL如下:

代码
select e.id, e.name, e.manager
from Employee e
where e.id = 1

insert into Employees (name, manager)
values ('Pierre Henri Kuate'null)
declare @newId int
select @newId = scope_identity()
update Employees 
set name = 'Tobin David Harris', manager = @newId
where id = 1

 

这个过程大体上从数据库中获取某个对象A,然后创建一个新的对象B,同时将对象A的Manger指向新的对象B。看一下生成的SQL,NH还是很聪明的。

注意到NH自动检测到对第一个Employee的Name和Manger的修改,这个NH的特性叫做automatic dirty checking,这个特性能够使你当修改某个Object的状态时不需要明确的调用NH来更新数据库,类似,可以看到创建的新的Employee(Pierre Henri),由于和Employee有关系,因而被自动保存。这个特性叫做cascading Save。 这个特性能够使你不需要明确的调用NH的Save方法,只要它和已经持久化的Object存在一定的关系(reachable), 同时注意到NH生成的SQL和更新对象的过程是不一致的,NH使用了一个非常复杂的算法来决定一个有效的顺序以避免外键约束。这个特性叫做 transactional write -behind。

  • Ø Runing the Program

Program.cs 大体如下所示:

代码
static void Main()
 {
            CreateEmployeeAndSaveToDatabase();
            UpdateTobinAndAssignPierreHenriAsManager();
            LoadEmployeesFromDatabase();
            Console.WriteLine(
"Press any key to exit...");
            Console.ReadKey();
 }

 

其他:

开始的时候没有按照NH In Action中例子完全做,使用了hibernate.cfg.xml文件而不是app.config,结果报出来说在Employee.hbm.xml文件中找不到dialct节点,当时奇怪极了,怀疑是嵌入资源的问题或者其他, 怎么可能找不到呢?手头也没有源码啥的。正好有NH IN Action 的代码,打开一看,没有啥不一样的。后来换用Class library加Test的方式,使用的仍然是hibernate.cfg.xml,居然通过了。突然醒悟,肯定是App.config的问题,一试,果然是。

在这个树的例子中作者说有个很复杂的算法可以保证啥啥的,呵呵,我还是弄了个死循环,结果NH没有检测出来,构造SQL也没有构造出来,死在那里了。和GSP一个模样。

  • 未解决的问题:
  • ² 由于多数判断是否同一记录的基本上都是用主键,NH是否存在较为快捷高效的方法来进行对比?
  • ² 留个疑问: 是否存在比较好的方式解决在以Database为中心的Application中由于Schema修改造成的影响?
  • ² 基于NHibernate的Application的展现部分有啥特殊之处?如果Database Schema或者Domain Class修改的话,是否会受到影响?还有一种情况就是记录的修改对于Object或者Database Schema产生的影响有多大?比如说枚举值的开始值发生变化了。