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 查询:

  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。

基础查询:

[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 用户手册》中。

时间: 2024-10-24 11:28:05

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

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

  Rafy 框架又添新成员:幽灵插件.本文将解释该插件的场景.使用方法.原理.   场景 在开发各类数据库应用系统时,往往需要在删除数据时不是真正地删除数据,而只是把数据标识为'已删除'状态.这些数据在业务逻辑上是已经完全删除.不可用的数据,但是不能在数据库中真正的把它们删除,而是需要永久保留这些历史数据.即开发人员常说的'假删除'功能. 这种需求往往是系统级的.往往不是针对某一张表,而很可能是针对系统中的所有表都需要实现'假删除'功能.   使用方法 由于这种需求比较常见,所以我们决定专门为

Rafy框架

l  什么是Rafy框架? -------- Rafy 是一个面向企业级开发的插件化快速开发框架. l  Rafy的优点是什么? ------快速开发.产品线工程.一套代码可同时生成并运行 C/S.单机版.B/S 三种应用程序. l  Rafy框架怎么用? Rafy User Guide : http://zgynhqf.github.io/Rafy --网页版的Rafy手册 上面这一套是我的学习思路. Rafy笔记 下面我把自己的学习笔记贴一下. 1.         n+1 问题的原因是懒加

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

为了提高开发者的易用性,Rafy 领域实体框架在很早开始就已经支持使用 Linq 语法来查询实体了.但是只支持了一些简单的.常用的条件查询,支持的力度很有限.特别是遇到对聚合对象的查询时,就不能再使用 Linq,而只能通过构造底层查询树的接口来完成了.由于开发者的聚合查询的需求越来越多,所以本周我们将这部分进行了增强. 接下来,本文将说明 Rafy 框架原来支持的 Linq 语法,以及最新加入的聚合查询支持及用法.   使用 Linq 查询的代码示例 public WarehouseList G

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

当我们开始使用EF的同时,是不是就会更好的认识了其他的ORM框架,最近接触了Rafy的使用,感觉还是蛮有兴趣去学习的,虽然最初的我到现在看的并不深入,但是我个人感觉还是可以简单地做一些总结的啦,或许语言不多,但是更多的想总结下它的使用,之前没接触的友友也可以简单地认识一下啦,嘿嘿. 要是想使用Rafy框架,其实很简单的,只需要打开VS然后在工具栏中选择扩展和更新然后搜索Rafy.SDK安装即可. Rafy框架的简单使用 说起来使用,其实还是很简单的入门的,当我们建立完我们的解决方案后我们就新建一

Rafy 框架 - 领域模型设计器(建模工具)设计方案

去年4月,我们为 Rafy 框架添加了领域模型设计器组件.时隔一年,谨以本文,简要说明该领域模型设计器的设计思想. 设计目标 Rafy 实体框架中以领域驱动设计作为指导思想.所以在开发时,以领域建模为首要任务.为此,我们为它开发了领域模型设计器.开发人员可以在设计器中,设计相应的领域模型,查看现有代码对应的领域模型. 我们为这个设计器制定了以下功能: 外部简单设计器:也就是设计器可以部署为一个可以独立运行的软件.该软件可以打开领域模型的设计图,方便团队中的非开发人员角色查看.同样,这个软件最好也

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

为了让开发者更方便地使用 Rafy 领域实体框架,本月,我们已经把最新版本的 Rafy 框架程序集发布到了 nuget.org 上,同时,还把 RafySDK 的最新版本发布到了 VisualStudio 插件仓库中. 以下说明如何下载.更新最新的 SDK 及程序集. 下载.更新最新的 RafySDK 在 VisualStudio 中打开扩展管理器(Tools -> Extensions and Updates),选择在线项目,并搜索 “Rafy” 安装即可.如下图: 同样,只需要在扩展管理器中

三大框架中模糊查询代码

js获取查询条件,并去后台查询 function queryByName(){                    //获取查询条件的用户名                    var queryName=$("#queryName").val();                    alert(queryName);                    //进行后台跳转                    window.location.href="<%=

TP框架中field查询字段

TP框架中field查询字段 不是fields 也不是files !!!! 不是fields 也不是files !!!! 不是fields 也不是files !!!! 不是fields 也不是files !!!! 不是fields 也不是files !!!! field

Rafy 框架 - 流水号插件

Rafy 框架又添新成员:流水号插件.本文将解释 Rafy 框架中的流水插件的场景.使用方法. 场景 在开发各类数据库应用系统时,往往需要生成从一开始的流水号,有时还需要按月或者按日进行独立生成,如下面的格式:2016031800000001.2016031800000002--. 设计本插件用于生成上述相应格式的编号. 使用方法 添加插件 1.通过 Nuget Package Manager 搜索并安装 Rafy.SerialNumber 插件. 2.在 DomainApp 中添加该插件:同时