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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网杂技

SQL注入攻防入门详解

毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机会接触相关开发……必须的各种借口。这几...

52210
来自专栏个人随笔

初识MySQL

sc delete mysql 删除服务! 一:数据库介绍 引入: 我们之前使用的数据都是存储在内存中的!比如说我们写一个注册功能。 我们首先需要在内存中创建...

3737
来自专栏黑泽君的专栏

day37_Spring学习笔记_05_CRM_01

CRM : custom releation manager 客户关系管理系统,用于维护客户和公司之间关系。 我们要做的是:学校 和 大家 之间关系。

712
来自专栏游戏杂谈

node-mysql操作transactions的实例

如果执行commit后,再执行node.js脚本(执行的是一句更新sql--UPDATE user_info SET userName = ‘张一’ WHERE...

1231
来自专栏*坤的Blog

注入学习1:SQL注入语句大全

之前做了xss预防,以及些许的注入预防了,但是不够全面,如果还是搜集了下一些常用的注入手段,以此用来进行更好的预防.

4242
来自专栏偏前端工程师的驿站

MyBatis魔法堂:Insert操作详解(返回主键、批量插入)

一、前言                                      数据库操作怎能少了INSERT操作呢?下面记录MyBatis关于INSERT...

8798
来自专栏林欣哲

MySQL数据库开发规范知识点速查

数据库设计规范 命名规范 基本设计规范 索引设计规范 字段设计规范 SQL开发规范 操作行为规范 命名规范 对象名称使用小写字母并用下划线分割 禁止使用MySQ...

40011
来自专栏DeveWork

WordPress主题下funtions.php的一段“恶意”代码

不多说了,相信有很多WordPresser都知道这段在WordPress主题下fountions.php的“恶意”代码,之所以为“恶意”加个双引号是想说:其实也...

30710
来自专栏肖蕾的博客

安卓端强大数据库ORM框架(XAndrDB)的使用及介绍XAndrDB一、新建数据库二、或者你这样新建数据库三、查询操作四、新增操作五、删除操作六、更新数据操作

1384
来自专栏Android Note

Android—Room数据库(介绍)

1505

扫码关注云+社区

领取腾讯云代金券