shenshuibomb 2009-09-22
在这里我们将对LINQ to SQL与NHibernate进行横向对比,通过对比大家能了解这两种方法的优劣,以及不同方法所获得的不同效果。
1 引言
研发与数据库打交道的系统的时候,最过于繁琐的莫过于没有编程快感的使用ADO.NET对后台数据库进行操作,因为所有的数据库连接、读取、操作千篇一律,编程成为了体力活。
虽然我们可以设计自己的类作为数据库访问的持久层,但是每一个类都必须有不相同的SQL语句,这样对于设计统一的数据库读写类造成了很大的困难。
开发人员在这种情况下必须包办窗体设计、方法设计、数据库读写设计的过程,这样加大了开发人员的负担也使得项目的维护和后期开发变得难以进行。
2 .NET下的ORM解决方案
2.1 LINQ
2.1.1 LINQ简介
作为微软开发的查询方案,LINQ 提供了一条更常规的途径即给 .Net Framework 添加一些可以应用于所有信息源( all sources of information )的具有多种用途( general-purpose )的语法查询特性( query facilities ),这是比向开发语言和运行时( runtime )添加一些关系数据( relational )特性或者类似 XML 特性( XML-specific )更好的方式。这些语法特性就叫做 .NET Language Integrated Query (LINQ) 。
如果觉得上面的解释有点抽象,那么可以这样理解,LINQ其实就是提供了一套查询功能,可以实现任何数据源的查询,此处数据源不单指数据库或者XML文件,而是任何集合或者实体,比如我们接触各种编程语言都需要用到的数组,现在不用遍历数组元素来寻找需要的项,LINQ可以实现这方面的查询。
LINQ查询数组:
图2.1 LINQ查询数组
上面是最简单的LINQ实现对数组的查询,泛型类型var在LINQ查询中提供了强大的委托类型支持,不管查询集合中项的类型(无论是int,char还是string或者类),我们只用一个var就可以保存LINQ查询到的结果。程序结果如下:
图2.2 LINQ查询数组程序结果
是不是很方便,LINQ的应用远远不这些,通过不同的映射方案,我们可以实现对数据库(LINQ To SQL),对XML文件(LINQ To XML)的访问。
2.1.2 LINQ简介
表2.1 LINQ的操作符
操作符 | 说明 |
聚合 | |
Aggregate | 对序列执行一个自定义方法 |
Average | 计算数值序列的平均值 |
Count | 返回序列中的项目数(整数) |
LongCount | 返回序列中的项目数(长型) |
Min | 查找数字序列中的最小数 |
Max | 查找数字序列中的最大数 |
Sum | 汇总序列中的数字 |
连接 | |
Concat | 将两个序列连成一个序列 |
转换 | |
Cast | 将序列中的元素转换成指定类型 |
OfType | 筛选序列中指定类型的元素 |
ToArray | 从序列返回一个数组 |
ToDictionary | 从序列返回一个字典 |
ToList | 从序列返回一个列表 |
ToLookup | 从序列返回一个查询 |
ToSequence | 返回一个 IEnumerable 序列 |
元素 | |
DefaultIfEmpty | 为空序列创建默认元素 |
ElementAt | 返回序列中指定索引的元素 |
ElementAtOrDefault | 返回序列中指定索引的元素,或者如果索引超出范围,则返回默认值 |
First | 返回序列中的第一个元素 |
FirstOrDefault | 返回序列中的第一个元素,或者如果未找到元素,则返回默认值 |
Last | 返回序列中的最后一个元素 |
LastOrDefault | 返回序列中的最后一个元素,或者如果未找到元素,则返回默认值 |
Single | 返回序列中的单个元素 |
SingleOrDefault | 返回序列中的单个元素,或者如果未找到元素,则返回默认值 |
相等 | |
SequenceEqual | 比较两个序列看其是否相等 |
生成 | |
Empty | 生成一个空序列 |
Range | 生成一个指定范围的序列 |
Repeat | 通过将某个项目重复指定次数来生成一个序列 |
分组 | |
GroupBy | 按指定分组方法对序列中的项目进行分组 |
联接 | |
GroupJoin | 通过归组将两个序列联接在一起 |
Join | 将两个序列从内部联接起来 |
排序 | |
OrderBy | 以升序按值排列序列 |
OrderByDescending | 以降序按值排列序列 |
ThenBy | 升序排列已排序的序列 |
ThenByDescending | 降序排列已排序的序列 |
Reverse | 颠倒序列中项目的顺序 |
分区 | |
Skip | 返回跳过指定数目项目的序列 |
SkipWhile | 返回跳过不满足表达式项目的序列 |
Take | 返回具有指定数目项目的序列 |
TakeWhile | 返回具有满足表达式项目的序列 |
投影 | |
Select | 创建部分序列的投影 |
SelectMany | 创建部分序列的一对多投影 |
限定符 | |
All | 确定序列中的所有项目是否满足某个条件 |
Any | 确定序列中是否有任何项目满足条件 |
Contains | 确定序列是否包含指定项目 |
限制 | |
Where | 筛选序列中的项目 |
设置 | |
Distinct | 返回无重复项目的序列 |
Except | 返回代表两个序列差集的序列 |
Intersect | 返回代表两个序列交集的序列 |
Union | 返回代表两个序列交集的序列 |
Lambda 表达式
许多标准查询操作符在对序列执行运算时都使用 Func 委托来处理单个元素。Lambda 表达式可与标准查询操作符结合使用以代表委托。lambda 表达式是创建委托实现的简略表达形式,并可用于匿名委托适用的所有场合。C# 和 Visual Basic® .NET 均支持 Lambda 表达式。但是,必须注意:由于 Visual Basic .NET 尚不支持匿名方法,Lambda 表达式可能仅包含一个语句。
上例中的的程序等同于下面
图2.3 Lambda表达式的使用
2.2 NHibernate
说到NHibernate,就不得不提Hibernate,原因很简单,Hibernate顾名思义就是Hibernate的.NET版本。
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。 Hibernate可以应用在任何使用JDBC的场合,既可以在Java的客户端程序使用,也可以在Servlet/JSP的Web应用中使用,最具革命意义的是,Hibernate可以在应用EJB的J2EE架构中取代CMP,完成数据持久化的重任。
NHibernate作为Hibernate的.NET应用于Hibernate的实现完全相同,学习NHibernate完全可以直接学习Hibernate的资料。
事实上,虽然在Java数据库映射领域Hibernate是使用最为广泛的方案,但是在.NET中由于LINQ等映射方案(包括微软下一代重量级的Entity Framework)的使用,NHibernate冷了许多。
NHibernate需要配置数据库配置文件和类/表映射配置文件,所以使用NHibernate需要懂得XML文件的基础知识,并且需要掌握比较复杂的XML文件配置节和相应的配置命令。
2.2.1 数据库配置文件
NHibernate官方提供了配置文件的模板和实例可供我们参考。
图2.4 NHibernate官方数据库配置文件模板(对应了不同的数据库)
上图为数据库配置文件。通常以“cfg.xml”作为后缀,一个示例的文件内容如下
图2.5 数据库配置文件示例
下面是一些在运行时可以改变NHibernate行为的其他配置。所有这些都是可选的,也有合理的默认值。
表2.2 NHibernate 配置属性
属性名 | 用途 |
hibernate.dialect | NHibernate方言(Dialect)的类名 - 可以让NHibernate使用某些特定的数据库平台的特性 例如: full.classname.of.Dialect(如果方言创建在NHibernate中), 或者full.classname.of.Dialect, assembly (如果使用一个自定义的方言的实现,它不属于NHibernate)。 |
hibernate.default_schema | 在生成的SQL中,scheml/tablespace的全限定名. 例如: SCHEMA_NAME |
hibernate.prepare_sql | 是否准备sql语句 例如: true | false |
hibernate.session_factory_name | SessionFactory被创建后将自动绑定这个名称. 例如: some.name |
hibernate.use_outer_join | 允许使用外连接抓取。 例如:true | false |
hibernate.cache.provider_class | 指定一个自定义的CacheProvider缓存提供者的类名 例如: full.classname.of.CacheProvider(如果ICacheProvider创建在NHibernate中), 或full.classname.of.CacheProvider, assembly(如果使用一个自定义的ICacheProvider,它不属于NHibernate)。 |
hibernate.query.substitutions | 把NHibernate查询中的一些短语替换为SQL短语(比如说短语可能是函数或者字符)。 例如: hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC |
2.2.2 实体映射配置文件
NHibernate官方开源包中提供了实体映射配置文件的实例可供我们参考。
图2.6 NHibernate开源包中提供的实体映射配置文件
与数据库配置文件一样实体映射配置文件也是XML文件(XML果然是很强大啊,微软下一代应用程序开发技术WPF就是使用XML文件将C/S和B/S长期分居的二人统一到一个屋檐下),所不同的是实体映射配置文件后缀是“hbm.xml”。
图2.7 实体映射配置文件
实体映射配置文件所要配置的信息一般为
Ø Schema
所有的XML映射都需要使用nhibernate-mapping-2.0 schema。目前的schema可以在NHibernate的资源路径或者是NHibernate.dll的嵌入资源(Embedded Resource)中找到。NHibernate总是会优先使用嵌入在资源中的schema文件。
Ø hibernate-mapping
(1) | schema (可选): 数据库schema名称. |
(2) | default-cascade (可选 - 默认为 none): 默认的级联风格. |
(3) | auto-import (可选 - 默认为 true): 指定是否我们可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。 |
(4) | default-access (可选 - 默认为 property): NHibernate访问属性值时的策略。 |
(5) | assembly (可选): 指定一个程序集,如果在映射文档中没有指定程序集,就使用这个程序集。 |
(6) | namespace (可选): 指定一个命名空间前缀,如果在映射文档中没有指定全限定名,就使用这个命名空间名。 |
Ø class
(1) | name: 持久化类(或者接口)的全限定名。 |
(2) | table: 对应的数据库表名。 |
(3) | discriminator-value (可选 - 默认和类名一样): 一个用于区分不同的子类的值,在多态行为时使用。 |
(4) | mutable (可选, 默认为 true): 表明该类的实例可变(不可变)。 |
(5) | schema (可选): 覆盖在根 元素中指定的schema名字。 |
(6) | proxy (可选): 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。 |
(7) | dynamic-update (可选, 默认为 false): 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段。 |
(8) | dynamic-insert (可选, 默认为 false): 指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。 |
(9) | polymorphism (可选, 默认为 implicit(隐式)): 界定是隐式还是显式的使用查询多态。 |
(10) | where (可选) 指定一个附加的SQL WHERE 条件,在抓取这个类的对象时会一直增加这个条件。 |
(11) | persister (可选): 指定一个定制的 IClassPersister. |
(12) | lazy(可选):假若设置 lazy="true",就是设置这个类自己的名字作为proxy接口的一种等价快捷形式。 |
Ø id
(1) | name (可选): 标识属性的名字。 |
(2) | type (可选): 标识NHibernate类型的名字。 |
(3) | column (可选 - 默认为属性名): 主键字段的名字。 |
(4) | unsaved-value (可选 - 默认为 null): 一个特定的标识属性值,用来标志该实例是刚刚创建的,尚未保存。这可以把这种实例和从以前的session中装载过(可能又做过修改--译者注)但未再次持久化的实例区分开来。 |
(5) | access (可选 - 默认为 property): NHibernate用来访问属性值的策略。 |
除此之外我们可以通过其他途径深入了解配置方面的知识,一个NHibernate项目,配置文件的错误往往导致错误的结果甚至使得程序无法运行。