Rafy 框架 - 使用 SqlTree 查询

本文介绍如何使用 Rafy 框架中的 Sql Tree 查询:
除了开发者常用的 Linq 查询,Rafy 框架还提供了 Sql 语法树的方式来进行查询。
这种查询方式下,开发者不需要直接编写真正的 Sql 语句,而是转而使用一套中间 Sql 语法树对象。这隔离了与具体数据库的耦合,使得开发者编写的查询可以跨越多种不同的数据库运行,甚至可以在非关系型数据库中运行。同时,框架还结合托管属性,提供了方便开发者使用的 API,并尽量保持与传统 Sql 相近的语法,使得开发者可以快速理解并编写。
本文包含以下章节:
- 快速示例
- 使用场景
- 代码段
- 更多示例
快速示例
SqlTree 查询是直接以一种类似于 Sql 语法的格式,并结合实体托管属性 IManagedProperty 来进行查询的查询模式。如下:
[RepositoryQuery]
public virtual ChapterList GetBy(string name, PagingInfo pi)
{
var f = QueryFactory.Instance; var t = f.Table<Chapter>(); var q = f.Query(
selection: f.SelectAll(),//查询所有列
from: t,//要查询的实体的表
where: t.Column(Chapter.NameProperty).Contains(name)//where 条件,
orderBy: new List<IOrderBy> {//排序
f.OrderBy(source.Column(Chapter.NameProperty), OrderDirection.Ascending)
}
); return (ChapterList)this.QueryData(q, pi);
}
可以看到,SqlTree 语法非常简单:
- 通过 QueryFactory.Instance 类型的单例对象来定义整个 SqlTree 查询对象。
- 查询中使用的是实体类型(Chapter)和实体的托管属性(Chapter.NameProperty)来定义表和字段。
更多的查询语法示例,见本节后面的更多示例。
使用场景
当您处于以下场景时,需要使用 SqlTree 查询:
- Linq 查询无法支持的一些场景。
Linq 查询目前只支持有限的一些操作符的解析,以及不太复杂的关系的分析。所以当您的查询较为复杂,已经无法使用 Linq 查询来实现时,可以考虑使用 SqlTree 查询。 - 需要更精确地控制 Sql 语句。
如果想要更加精确地控制最终生成的 Sql 语句,也需要使用 SqlTree。例如,Linq 查询中需要两个实体有确切的实体关系才会最终生成 Join 语句;但是 SqlTree 则与 Sql 语句无异,开发者可以随意将两个实体对应的表进行 Join 操作。
- 需要更好的性能。
SqlTree 查询是 Rafy 框架查询数据(表格、实体)的核心实现。在框架底层,Linq 查询也都是完全是基于 SqlTree 查询来实现的。当开发者在使用 Linq 查询时,编译器其实是生成一组对象来表示一棵表达式树,而 Rafy 框架会解析这棵树,生成更加底层的 SqlTree 对象,才交给执行引擎去生成真正的 Sql 语句并最终执行。所以,直接使用 SqlTree 则节约了表达式树的生成(大量反射与对象)与解析的性能消耗。同样,Rafy 没有象 Hibernate 框架定义一套新的基于字符串的查询语法(如 hql),也是因为开发者编写 hql,不但无法得到编译时的语法支持,而且性能上也需要消耗对 hql 进行解析并生成 SqlTree,不如直接使用更直接的 SqlTree。
当然,Rafy 在 SqlTree 的基础上再推出 Linq 查询的原因,是因为 SqlTree 本身需要一定的学习周期才能使用,而开发者则更熟悉使用 Linq 语法进行查询,基本可以认为是上手即用,所以支持 Linq 查询可以简化大部分的简单开发场景。
- 希望编写更通用的查询。
仓库基类 EntityRepository 中自带的 GetAll、GetById 等方法,都是面向所有实体类型的非常通用的查询。对于基于 Rafy 的上层框架的开发者而言,除了直接使用这些自带的通用查询,很多时候是需要自行编写一些类似的通用查询的。Linq 的 Labmda 语法中的属性表达式(e.Name)需要绑定具体的实体类型(Book e),这导致了必须使用反射去生成表达式树,才能编写通条蚁。但是,SqlTree 的语法是基于托管属性框架的,它不需要使用确切的实体属性表达式,只需要使用托管属性的运行时对象 IManagedProperty 即可(Book.NameProperty)。这使得开发人员可以更加方便地编写通用查询。例如,仓库基类 EntityRepository 中的所有查询方法,都是直接通过使用实体的托管属性来实现的,例如:GetById、GetByParentId、GetAll 等。
- 可以为扩展属性编写查询。
由于扩展属性写在额外的程序集插件中的,所以当无法通过 Linq 表达式进行查询。这时就不得不通过托管属性 IManagedProperty 来定义 SqlTree 完成查询了。关于扩展属性,参见:扩展属性。
- 支持多个数据库。
上述的场景中,其实还可以直接编写 Sql 语句来进行查询。但是这样就很难保证开发者编写的 Sql 语句能够在多个数据库上能够正确运行。 - 查询需要支持仓库数据层的扩展点。
由于 Rafy 的查询核心都是基于 SqlTree 来实现的,所以内部的所有扩展点都是要依赖 SqlTree的。如果开发者直接编写 Sql 语句来查询,那么这些许多的扩展点都将无效,无法对开发者编写的这条 Sql 语句进行扩展。例如:当使用 幽灵插件 对所有幽灵数据进行自动过滤时,如果开发者使用手工编写的 Sql 语法进行查询,那么自动过滤功能无效,需要开发者自己进行幽灵数据的过滤。
代码段
RafySDK 中提供了两个代码段,来辅助开发者生成基本的 SqlTree 查询结构:Rafy_Query、Rafy_Query_TableQueryContent。
详情见:代码段。
更多示例
下面将会列出一些常见的 SqlTree 查询示例。通过这些代码,您将学习到如何在各种查询需求下使用 SqlTree。
基础查询:
[RepositoryQuery]
public virtual ChapterList GetBy(string name, PagingInfo pi)
{
var f = QueryFactory.Instance; var t = f.Table<Chapter>(); var q = f.Query(
//selection: f.SelectAll(),//没有 selection,则默认表示查询所有列
from: t,//要查询的实体的表
where: t.Column(Chapter.NameProperty).Contains(name)//where 条件
); return (ChapterList)this.QueryData(q, pi);
}
表格数据查询:
[RepositoryQuery]
public virtual LiteDataTable GetBy(string name, PagingInfo pi)
{
var f = QueryFactory.Instance; var t = f.Table<Chapter>(); var q = f.Query(
from: t,
where: t.Column(Chapter.NameProperty).Contains(name)
); return this.QueryTable(q, pi);//由查询实体变为查询数据表格,只是更换了这一行代码。
} 两个列的条件进行比较:
var table = f.Table(this);//使用当前的仓库来表示当前的表
var q = f.Query(
from :table,
where: table.Column(Chapter.NameProperty).Equal(table.Column(Chapter.CodeProperty))//两个列相等
); 使用 And、Or:
var table = f.Table(this);
var q = f.Query(
from :table,
where: f.And(
table.Column(Chapter.NameProperty).Equal(name),
f.Or(
table.Column(Chapter.IdProperty).LessEqual(10),
table.Column(Chapter.IdProperty).GreaterEqual(1000)
)
)
);
Join(SerialNumberValueRepository 中的真实代码):
/// <summary>
/// 获取某个规则下最新的一个值。
/// </summary>
/// <param name="autoCodeName"></param>
/// <returns></returns>
[RepositoryQuery]
public virtual SerialNumberValue GetLastValue(string autoCodeName)
{
var f = QueryFactory.Instance;
var t = f.Table<SerialNumberValue>();
var t2 = f.Table<SerialNumberInfo>();
var q = f.Query(
from: t.Join(t2),//由于 SerialNumberValue 有一个 SerialNumberInfo 的引用属性,则在使用 Join 时,不需要给出 Join 的条件。
where: t2.Column(SerialNumberInfo.NameProperty).Equal(autoCodeName),
orderBy: new List<IOrderBy> { f.OrderBy(t.Column(SerialNumberValue.LastUpdatedTimeProperty), OrderDirection.Descending) }
); return (SerialNumberValue)this.QueryData(q);
}
使用完整的 Join:
var t = f.Table<SerialNumberValue>();
var t2 = f.Table<SerialNumberInfo>();
var q = f.Query(
from: t.Join(t2, t.Column(SerialNumberValue.SerialNumberInfoIdProperty).Equal(t2.Column(SerialNumberInfo.IdProperty)), JoinType.Inner),//不但可以给出具体的 Join 条件,还可以给出 Join 类型。
where: t2.Column(SerialNumberInfo.NameProperty).Equal(autoCodeName),
orderBy: new List<IOrderBy> { f.OrderBy(t.Column(SerialNumberValue.LastUpdatedTimeProperty), OrderDirection.Descending) }
);
Exists:
var bookTable = f.Table(this);
var chapterTable = f.Table<Chapter>();
var q = f.Query(
from: bookTable,
where: f.Exists(f.Query(
from: chapterTable,
where: chapterTable.Column(Chapter.BookIdProperty).Equal(bookTable.IdColumn)
))
);
Not Exists:
var book = f.Table(this);
var chapter = f.Table<Chapter>();
var q = f.Query(
from: book,
where: f.Not(f.Exists(f.Query(
from: chapter,
where: f.And(
f.Constraint(chapter.Column(Chapter.BookIdProperty), book.IdColumn),
f.Constraint(chapter.Column(Chapter.NameProperty), PropertyOperator.NotEqual, chapterName)
)
)))
);
更多示例,请参照源码中单元测试的 ORMTest 中的 TableQuery 相关方法。
PS:该文已经纳入《 Rafy 用户手册》中。
Rafy 框架 - 使用 SqlTree 查询的更多相关文章
- Rafy 框架 - 通用查询条件(CommonQueryCriteria)
在应用开发过程中,有 80% 的场景下,开发者所需要的实体查询,查询条件中其实都是一些简单的属性匹配,又或是一些属性匹配的简单组合.Rafy 为这样的场景提供了更为方便使用的 API:CommonQu ...
- Rafy 框架 - 幽灵插件(假删除)
Rafy 框架又添新成员:幽灵插件.本文将解释该插件的场景.使用方法.原理. 场景 在开发各类数据库应用系统时,往往需要在删除数据时不是真正地删除数据,而只是把数据标识为'已删除'状态.这些数 ...
- Rafy 中的 Linq 查询支持(根据聚合子条件查询聚合父)
为了提高开发者的易用性,Rafy 领域实体框架在很早开始就已经支持使用 Linq 语法来查询实体了.但是只支持了一些简单的.常用的条件查询,支持的力度很有限.特别是遇到对聚合对象的查询时,就不能再使用 ...
- 快速开发~Rafy框架的初步认识
当我们开始使用EF的同时,是不是就会更好的认识了其他的ORM框架,最近接触了Rafy的使用,感觉还是蛮有兴趣去学习的,虽然最初的我到现在看的并不深入,但是我个人感觉还是可以简单地做一些总结的啦,或许语 ...
- Rafy 框架 - 时间戳插件
本文将解释 Rafy 框架中的时间戳插件的场景.使用方法.原理. 场景 在开发各类数据库应用系统时,业务领域实体往往需要包含"创建时间"."最后更新时间".&q ...
- Rafy框架
l 什么是Rafy框架? -------- Rafy 是一个面向企业级开发的插件化快速开发框架. l Rafy的优点是什么? ------快速开发.产品线工程.一套代码可同时生成并运行 C/S.单 ...
- Rafy 框架 - 实体支持只更新部分变更的字段
Rafy 快一两年没有大的更新了.并不是这个框架没人维护了.相反,主要是因为自己的项目.以及公司在使用的项目,都已经比较稳定了,也没有新的功能添加.但是最近因为外面使用了 Rafy 的几个公司,找到我 ...
- Rafy 框架:领域控制器
本文简要说明如何使用 Rafy 框架中的领域控制器. 简介 领域控制器是 Rafy 框架中用于封装领域逻辑的主要方式. 在控制器中,开发者可以封装大量的业务逻辑,并向外暴露业务接口.内部的逻辑在实现时 ...
- 使用 NuGet 下载最新的 Rafy 框架及文档
为了让开发者更方便地使用 Rafy 领域实体框架,本月,我们已经把最新版本的 Rafy 框架程序集发布到了 nuget.org 上,同时,还把 RafySDK 的最新版本发布到了 VisualStud ...
随机推荐
- 红黑树——算法导论(15)
1. 什么是红黑树 (1) 简介 上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...
- 120项改进:开源超级爬虫Hawk 2.0 重磅发布!
沙漠君在历时半年,修改无数bug,更新一票新功能后,在今天隆重推出最新改进的超级爬虫Hawk 2.0! 啥?你不知道Hawk干吗用的? 这是采集数据的挖掘机,网络猎杀的重狙!半年多以前,沙漠君写了一篇 ...
- RabbitMq应用一
RabbitMq应用一 RabbitMQ的具体概念,百度百科一下,我这里说一下我的理解,如果有少或者不对的地方,欢迎纠正和补充. 一个项目架构,小的时候,一般都是传统的单一网站系统,或者项目,三层架构 ...
- Oracle学习之路-- 案例分析实现行列转换的几种方式
注:本文使用的数据库表为oracle自带scott用户下的emp,dept等表结构. 通过一个例子来说明行列转换: 需求:查询每个部门中各个职位的总工资 按我们最原始的思路可能会这么写: ...
- javascript设计模式:策略模式
前言 策略模式有效利用组合.委托.多态等技术和思想,可以有效避免多重条件选择语句. 策略模式对开放-封闭原则提供了很好的支持,将算法封装在strategy中,使得他们易于切换.理解.扩展. 策略模式中 ...
- 微信小程序中利用时间选择器和js无计算实现定时器(将字符串或秒数转换成倒计时)
转载注明出处 改成了一个单独的js文件,并修改代码增加了通用性,点击这里查看 今天写小程序,有一个需求就是用户选择时间,然后我这边就要开始倒计时. 因为小程序的限制,所以直接选用时间选择器作为选择定时 ...
- CommandPattern
/** * 命令模式 * @author TMAC-J * 将调用者和接受者分离 * 可以将一组命令组合在一起,适合很多命令的时候 */ public class CommandPattern { i ...
- 分布式学习系列【dubbo入门实践】
分布式学习系列[dubbo入门实践] dubbo架构 组成部分:provider,consumer,registry,monitor: provider,consumer注册,订阅类似于消息队列的注册 ...
- 在 SharePoint Server 2016 本地环境中设置 OneDrive for Business
建议补丁 建议在sharepoint2016打上KB3127940补丁,补丁下载地址 https://support.microsoft.com/zh-cn/kb/3127940 当然不打,也可以用O ...
- Linux基础介绍【第一篇】
Linux简介 什么是操作系统? 操作系统,英文名称Operating System,简称OS,是计算机系统中必不可少的基础系统软件,它是应用程序运行以及用户操作必备的基础环境支撑,是计算机系统的核心 ...