对象化查询之OCL vs. OPath
令人心动的对象化查询
对象化查询是O/R Mapping中非常让人心动的一个特性,当所有的表对我们来说不复存在后,我们可以对实体进行CRUD,可是当我们需要返回多个实体的结果集时,我们怎么办呢?Dataset做为载体是必然的,可是我们查询过滤数据的方式是怎样的?sql?No!我们就是在尽量避免直接使用sql,因为sql不便于重构,可移植性太差,拼凑出来的sql语句对测试覆盖率也有影响。所以大家都期待着可以用对象化查询来根除将Entity再当做Table来处理的尴尬。
.Net下的O/R Mapper对对象化查询的支持
目前为止,.Net下的O/R Mapper对对象化查询的支持绝大部分都是非常有限的:NHibernate用的是HQL语句,其本质可以说是一种把Entity看做Table的改装后的sql语句,DataObjects.Net和NHibernate差不多,也有一套自己的类sql对象化查询语法,其它很多O/R Mapper使用的是简单的属性过滤查询,而说到对对象化查询支持得最强大的,要算ECOⅡ和ObjectSpaces,前者使用的是OCL语法,后者是OPath。
OCL起源
OCL是OMG组织制订的UML语言规范的一部分,它的初衷是用来对UML模型视图中的Object添加约束描述的,并不是一种编程语言,而OCL应用到O/R Mapping中就俨然成了一种对对象进行查询的语言(OQL)。
OPath起源
OPath是MS提出的在WinFS中准备大量使用的一种对象查询语言,从它的命名上可以看出和XPath的相似之处,MS的口号一直是把复杂的东西变简单,把简单的东西变成自动化。所以这个OPath确实不可小觑。
简单的例子
先我们从一个简单的例子入手,假设有如下的实体关系(画图太麻烦,还浪费空间):
Department (code name employees)
Employee(code name department salary)
查询 |
OCL |
OPath |
包含薪水大于3000的员工所有部门 |
Department.allInstances->select(self.employees.includes(salary > 3000)) |
Exists(employees[saleary > 3000]) |
得到部门001的所有员工 |
Employee.allInstances->select(self.department.code = ‘001’) |
Department.code = ‘001’ |
所有员工薪水都大于3000的部门 |
Department.allInstances->select(self.employees.forAll(salary > 3000)) |
不支持 |
员工最高薪水大于3000的部门 |
Department.allInstances->select(self.employees.includes(salary > 3000)) |
不支持 |
功能比较
从这个列表中可以看到OPath的语法要简洁得多,但OCL的集合操纵能力却是OPath无法比拟的,下面是OCL的一些集合函数:
名称 |
功能 |
Collect |
得到一个属性的集合 |
Exists |
如果存在符合条件的元素为真 |
forAll |
只有所有集合元素符合条件时才为真 |
Includes |
是否包含某个对象 |
Select |
获取符合条件的一个子集 |
Reject |
减去符合条件的一个子集 |
IsEmpty |
集合为空 |
NotEmpty |
集合不为空 |
此外,ECOⅡ对集合操作还有增强,如提供了sum、avg、max、min的聚集操作函数,同时它还支持多种非聚集函数,如concat、length、toupper、tolower等。
其它的一些特性:
特性 |
OCL |
OPath |
NULL判断 |
支持 |
支持 |
Implies(表示如果左边为真,那么右边也要为真) |
支持 |
A implies B可以转化为(A and B) or (Not A) |
类型判断和转换 |
支持 |
无 |
Distinct |
支持(通过Set集合返回) |
不支持(因为只是对象获取和属性过滤,没有集合操作,所以不涉及到子集合,而外部对象集合一般有主键约束) |
集合内元素获取 |
支持 |
不支持 |
Order By |
支持 |
不支持,但可以通过调用查询对象的方法达到目的 |
假如增强OPath的话,大概会是什么样子?
如果要支持集合操作的话,那么现有的OPath语法的简单性肯定要大打折扣,比如说类似于OCL中的forAll,OPath如果要实现的话,可能的方法就是:
(Employees[salary > 3000].Count = Employees.Count)
性能比较
因为现在ObjectSpaces中的OPath语句最后都将转为SQL执行,所以它的性能是非常好的(时这也正是它功能无法强大的弱点),而OCL的语法只有部分才能转化为SQL,因此在ECOⅡ中,还有部门的OCL操作必须针对内存对象来进行,这样也就使得必须装载大量的实体对象到内存中。
你将选择哪种查询?
就我个人而言,我倾向于增强型的OPath。毕竟更加复杂的查询我们可以通过view和store procedure来完成,就算OCL再强大,也无法涵盖我们需要的查询功能,其内存对象操纵的性能问题不能不让我们谨慎使用,而ECOⅡ可以算是一个MDA的框架,这也让我对MDA的将来感到一些担忧,除非ANSI SQL进化到了可以更加强大的地步,各数据库本身就可以完成OCL所需要的查询功能,但这个时候,也许SQL的表达更加直观,那么,我们又为什么需要OCL来查询呢。当然,OCL在UML中的作用我们并不否定,只是直接从模型生成代码的应用似乎只能在不太复杂的场合才生效。
(注:因为ECOⅡ的资料很少,很多是参考它的前身bold for delphi的语法,而OPath的资料也少,很多都是无关痛痒的简单操作,这让人弄不明白到底是它的语法就这么简单还是另有其它的增强。)