本文介绍如何使用 Rafy 框架中的 Sql Tree 查询:

除了开发者常用的 Linq 查询,Rafy 框架还提供了 Sql 语法树的方式来进行查询。

这种查询方式下,开发者不需要直接编写真正的 Sql 语句,而是转而使用一套中间 Sql 语法树对象。这隔离了与具体数据库的耦合,使得开发者编写的查询可以跨越多种不同的数据库运行,甚至可以在非关系型数据库中运行。同时,框架还结合托管属性,提供了方便开发者使用的 API,并尽量保持与传统 Sql 相近的语法,使得开发者可以快速理解并编写。

本文包含以下章节:

  • 快速示例
  • 使用场景
  • 代码段
  • 更多示例

快速示例


SqlTree 查询是直接以一种类似于 Sql 语法的格式,并结合实体托管属性 IManagedProperty 来进行查询的查询模式。如下:

  1. [RepositoryQuery]
  2. public virtual ChapterList GetBy(string name, PagingInfo pi)
  3. {
  4. var f = QueryFactory.Instance;
  5.  
  6. var t = f.Table<Chapter>();
  7.  
  8. var q = f.Query(
  9. selection: f.SelectAll(),//查询所有列
  10. from: t,//要查询的实体的表
  11. where: t.Column(Chapter.NameProperty).Contains(name)//where 条件,
  12. orderBy: new List<IOrderBy> {//排序
  13. f.OrderBy(source.Column(Chapter.NameProperty), OrderDirection.Ascending)
  14. }
  15. );
  16.  
  17. return (ChapterList)this.QueryData(q, pi);
  18. }

可以看到,SqlTree 语法非常简单:

  • 通过 QueryFactory.Instance 类型的单例对象来定义整个 SqlTree 查询对象。
  • 查询中使用的是实体类型(Chapter)和实体的托管属性(Chapter.NameProperty)来定义表和字段。

更多的查询语法示例,见本节后面的更多示例。

 

使用场景


当您处于以下场景时,需要使用 SqlTree 查询:

  1. Linq 查询无法支持的一些场景。
    Linq 查询目前只支持有限的一些操作符的解析,以及不太复杂的关系的分析。所以当您的查询较为复杂,已经无法使用 Linq 查询来实现时,可以考虑使用 SqlTree 查询。
  2. 需要更精确地控制 Sql 语句。

    如果想要更加精确地控制最终生成的 Sql 语句,也需要使用 SqlTree。

    例如,Linq 查询中需要两个实体有确切的实体关系才会最终生成 Join 语句;但是 SqlTree 则与 Sql 语句无异,开发者可以随意将两个实体对应的表进行 Join 操作。

  3. 需要更好的性能。

    SqlTree 查询是 Rafy 框架查询数据(表格、实体)的核心实现。在框架底层,Linq 查询也都是完全是基于 SqlTree 查询来实现的。当开发者在使用 Linq 查询时,编译器其实是生成一组对象来表示一棵表达式树,而 Rafy 框架会解析这棵树,生成更加底层的 SqlTree 对象,才交给执行引擎去生成真正的 Sql 语句并最终执行。所以,直接使用 SqlTree 则节约了表达式树的生成(大量反射与对象)与解析的性能消耗。

    同样,Rafy 没有象 Hibernate 框架定义一套新的基于字符串的查询语法(如 hql),也是因为开发者编写 hql,不但无法得到编译时的语法支持,而且性能上也需要消耗对 hql 进行解析并生成 SqlTree,不如直接使用更直接的 SqlTree。

    当然,Rafy 在 SqlTree 的基础上再推出 Linq 查询的原因,是因为 SqlTree 本身需要一定的学习周期才能使用,而开发者则更熟悉使用 Linq 语法进行查询,基本可以认为是上手即用,所以支持 Linq 查询可以简化大部分的简单开发场景。

  4. 希望编写更通用的查询。

    仓库基类 EntityRepository 中自带的 GetAll、GetById 等方法,都是面向所有实体类型的非常通用的查询。对于基于 Rafy 的上层框架的开发者而言,除了直接使用这些自带的通用查询,很多时候是需要自行编写一些类似的通用查询的。

    Linq 的 Labmda 语法中的属性表达式(e.Name)需要绑定具体的实体类型(Book e),这导致了必须使用反射去生成表达式树,才能编写通条蚁。但是,SqlTree 的语法是基于托管属性框架的,它不需要使用确切的实体属性表达式,只需要使用托管属性的运行时对象 IManagedProperty 即可(Book.NameProperty)。这使得开发人员可以更加方便地编写通用查询。例如,仓库基类 EntityRepository 中的所有查询方法,都是直接通过使用实体的托管属性来实现的,例如:GetById、GetByParentId、GetAll 等。

  5. 可以为扩展属性编写查询。

    由于扩展属性写在额外的程序集插件中的,所以当无法通过 Linq 表达式进行查询。这时就不得不通过托管属性 IManagedProperty 来定义 SqlTree 完成查询了。

    关于扩展属性,参见:扩展属性

  6. 支持多个数据库。

    上述的场景中,其实还可以直接编写 Sql 语句来进行查询。但是这样就很难保证开发者编写的 Sql 语句能够在多个数据库上能够正确运行。
  7. 查询需要支持仓库数据层的扩展点。

    由于 Rafy 的查询核心都是基于 SqlTree 来实现的,所以内部的所有扩展点都是要依赖 SqlTree的。如果开发者直接编写 Sql 语句来查询,那么这些许多的扩展点都将无效,无法对开发者编写的这条 Sql 语句进行扩展。

    例如:当使用 幽灵插件 对所有幽灵数据进行自动过滤时,如果开发者使用手工编写的 Sql 语法进行查询,那么自动过滤功能无效,需要开发者自己进行幽灵数据的过滤。

代码段


RafySDK 中提供了两个代码段,来辅助开发者生成基本的 SqlTree 查询结构:Rafy_Query、Rafy_Query_TableQueryContent。

详情见:代码段

更多示例


下面将会列出一些常见的 SqlTree 查询示例。通过这些代码,您将学习到如何在各种查询需求下使用 SqlTree。

基础查询:

  1. [RepositoryQuery]
  2. public virtual ChapterList GetBy(string name, PagingInfo pi)
  3. {
  4. var f = QueryFactory.Instance;
  5.  
  6. var t = f.Table<Chapter>();
  7.  
  8. var q = f.Query(
  9. //selection: f.SelectAll(),//没有 selection,则默认表示查询所有列
  10. from: t,//要查询的实体的表
  11. where: t.Column(Chapter.NameProperty).Contains(name)//where 条件
  12. );
  13.  
  14. return (ChapterList)this.QueryData(q, pi);
  15. }

表格数据查询:

  1. [RepositoryQuery]
  2. public virtual LiteDataTable GetBy(string name, PagingInfo pi)
  3. {
  4. var f = QueryFactory.Instance;
  5.  
  6. var t = f.Table<Chapter>();
  7.  
  8. var q = f.Query(
  9. from: t,
  10. where: t.Column(Chapter.NameProperty).Contains(name)
  11. );
  12.  
  13. return this.QueryTable(q, pi);//由查询实体变为查询数据表格,只是更换了这一行代码。
  14. }
  15.  
  16. 两个列的条件进行比较:
  1. var table = f.Table(this);//使用当前的仓库来表示当前的表
  2. var q = f.Query(
  3. from :table,
  4. where: table.Column(Chapter.NameProperty).Equal(table.Column(Chapter.CodeProperty))//两个列相等
  5. );
  6.  
  7. 使用 AndOr
  1. var table = f.Table(this);
  2. var q = f.Query(
  3. from :table,
  4. where: f.And(
  5. table.Column(Chapter.NameProperty).Equal(name),
  6. f.Or(
  7. table.Column(Chapter.IdProperty).LessEqual(10),
  8. table.Column(Chapter.IdProperty).GreaterEqual(1000)
  9. )
  10. )
  11. );

Join(SerialNumberValueRepository 中的真实代码):

  1. /// <summary>
  2. /// 获取某个规则下最新的一个值。
  3. /// </summary>
  4. /// <param name="autoCodeName"></param>
  5. /// <returns></returns>
  6. [RepositoryQuery]
  7. public virtual SerialNumberValue GetLastValue(string autoCodeName)
  8. {
  9. var f = QueryFactory.Instance;
  10. var t = f.Table<SerialNumberValue>();
  11. var t2 = f.Table<SerialNumberInfo>();
  12. var q = f.Query(
  13. from: t.Join(t2),//由于 SerialNumberValue 有一个 SerialNumberInfo 的引用属性,则在使用 Join 时,不需要给出 Join 的条件。
  14. where: t2.Column(SerialNumberInfo.NameProperty).Equal(autoCodeName),
  15. orderBy: new List<IOrderBy> { f.OrderBy(t.Column(SerialNumberValue.LastUpdatedTimeProperty), OrderDirection.Descending) }
  16. );
  17.  
  18. return (SerialNumberValue)this.QueryData(q);
  19. }

使用完整的 Join:

  1. var t = f.Table<SerialNumberValue>();
  2. var t2 = f.Table<SerialNumberInfo>();
  3. var q = f.Query(
  4. from: t.Join(t2, t.Column(SerialNumberValue.SerialNumberInfoIdProperty).Equal(t2.Column(SerialNumberInfo.IdProperty)), JoinType.Inner),//不但可以给出具体的 Join 条件,还可以给出 Join 类型。
  5. where: t2.Column(SerialNumberInfo.NameProperty).Equal(autoCodeName),
  6. orderBy: new List<IOrderBy> { f.OrderBy(t.Column(SerialNumberValue.LastUpdatedTimeProperty), OrderDirection.Descending) }
  7. );

Exists:

  1. var bookTable = f.Table(this);
  2. var chapterTable = f.Table<Chapter>();
  3. var q = f.Query(
  4. from: bookTable,
  5. where: f.Exists(f.Query(
  6. from: chapterTable,
  7. where: chapterTable.Column(Chapter.BookIdProperty).Equal(bookTable.IdColumn)
  8. ))
  9. );

Not Exists:

  1. var book = f.Table(this);
  2. var chapter = f.Table<Chapter>();
  3. var q = f.Query(
  4. from: book,
  5. where: f.Not(f.Exists(f.Query(
  6. from: chapter,
  7. where: f.And(
  8. f.Constraint(chapter.Column(Chapter.BookIdProperty), book.IdColumn),
  9. f.Constraint(chapter.Column(Chapter.NameProperty), PropertyOperator.NotEqual, chapterName)
  10. )
  11. )))
  12. );

更多示例,请参照源码中单元测试的 ORMTest 中的 TableQuery 相关方法。

PS:该文已经纳入《 Rafy 用户手册》中。


Rafy 框架 - 使用 SqlTree 查询的更多相关文章

  1. Rafy 框架 - 通用查询条件(CommonQueryCriteria)

    在应用开发过程中,有 80% 的场景下,开发者所需要的实体查询,查询条件中其实都是一些简单的属性匹配,又或是一些属性匹配的简单组合.Rafy 为这样的场景提供了更为方便使用的 API:CommonQu ...

  2. Rafy 框架 - 幽灵插件(假删除)

      Rafy 框架又添新成员:幽灵插件.本文将解释该插件的场景.使用方法.原理.   场景 在开发各类数据库应用系统时,往往需要在删除数据时不是真正地删除数据,而只是把数据标识为'已删除'状态.这些数 ...

  3. Rafy 中的 Linq 查询支持(根据聚合子条件查询聚合父)

    为了提高开发者的易用性,Rafy 领域实体框架在很早开始就已经支持使用 Linq 语法来查询实体了.但是只支持了一些简单的.常用的条件查询,支持的力度很有限.特别是遇到对聚合对象的查询时,就不能再使用 ...

  4. 快速开发~Rafy框架的初步认识

    当我们开始使用EF的同时,是不是就会更好的认识了其他的ORM框架,最近接触了Rafy的使用,感觉还是蛮有兴趣去学习的,虽然最初的我到现在看的并不深入,但是我个人感觉还是可以简单地做一些总结的啦,或许语 ...

  5. Rafy 框架 - 时间戳插件

    本文将解释 Rafy 框架中的时间戳插件的场景.使用方法.原理. 场景 在开发各类数据库应用系统时,业务领域实体往往需要包含"创建时间"."最后更新时间".&q ...

  6. Rafy框架

    l  什么是Rafy框架? -------- Rafy 是一个面向企业级开发的插件化快速开发框架. l  Rafy的优点是什么? ------快速开发.产品线工程.一套代码可同时生成并运行 C/S.单 ...

  7. Rafy 框架 - 实体支持只更新部分变更的字段

    Rafy 快一两年没有大的更新了.并不是这个框架没人维护了.相反,主要是因为自己的项目.以及公司在使用的项目,都已经比较稳定了,也没有新的功能添加.但是最近因为外面使用了 Rafy 的几个公司,找到我 ...

  8. Rafy 框架:领域控制器

    本文简要说明如何使用 Rafy 框架中的领域控制器. 简介 领域控制器是 Rafy 框架中用于封装领域逻辑的主要方式. 在控制器中,开发者可以封装大量的业务逻辑,并向外暴露业务接口.内部的逻辑在实现时 ...

  9. 使用 NuGet 下载最新的 Rafy 框架及文档

    为了让开发者更方便地使用 Rafy 领域实体框架,本月,我们已经把最新版本的 Rafy 框架程序集发布到了 nuget.org 上,同时,还把 RafySDK 的最新版本发布到了 VisualStud ...

随机推荐

  1. 隐马尔科夫模型python实现简单拼音输入法

    在网上看到一篇关于隐马尔科夫模型的介绍,觉得简直不能再神奇,又在网上找到大神的一篇关于如何用隐马尔可夫模型实现中文拼音输入的博客,无奈大神没给可以运行的代码,只能纯手动网上找到了结巴分词的词库,根据此 ...

  2. 使用Oracle官方巡检工具ORAchk巡检数据库

    ORAchk概述 ORAchk是Oracle官方出品的Oracle产品健康检查工具,可以从MOS(My Oracle Support)网站上下载,免费使用.这个工具可以检查Oracle数据库,Gold ...

  3. 将 instance 部署到 OVS Local Network - 每天5分钟玩转 OpenStack(130)

    上一节创建了 OVS 本地网络 first_local_net,今天我们会部署一个 instance 到该网络并分析网络结构.launch 一个 instance,选择 first_local_net ...

  4. [.NET] C# 知识回顾 - 委托 delegate (续)

    C# 知识回顾 - 委托 delegate (续) [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/6046171.html 序 上篇<C# 知识回 ...

  5. iOS开发之App间账号共享与SDK封装

    上篇博客<iOS逆向工程之KeyChain与Snoop-it>中已经提到了,App间的数据共享可以使用KeyChian来实现.本篇博客就实战一下呢.开门见山,本篇博客会封装一个登录用的SD ...

  6. 快递Api接口 & 微信公众号开发流程

    之前的文章,已经分析过快递Api接口可能被使用的需求及场景:今天呢,简单给大家介绍一下微信公众号中怎么来使用快递Api接口,来完成我们的需求和业务场景. 开发语言:Nodejs,其中用到了Neo4j图 ...

  7. Java 时间类-Calendar、Date、LocalDate/LocalTime

    1.Date 类 java.util.Date是一个"万能接口",它包含日期.时间,还有毫秒数,如果你只想用java.util.Date存储日期,或者只存储时间,那么,只有你知道哪 ...

  8. windows10安装mysql5.7.17是这样安装的吗?

    操作 全允许

  9. 2015微软MVP全球峰会见闻

    2015.10.31-2015.11.8 一周的时间完成微软MVP全球峰会旅程,这一周在不断的倒时差,行程安排非常的紧张,还好和大家请假了没有更新微信公众号,今天开始继续更新微信公众号,开始新的旅程, ...

  10. Windows10-UWP中设备序列显示不同XAML的三种方式[3]

    阅读目录: 概述 DeviceFamily-Type文件夹 DeviceFamily-Type扩展 InitializeComponent重载 结论 概述 Windows10-UWP(Universa ...