LINQ超过功能方法链的好处

在Kotlin Slack上讨论了添加代码树来支持像C#LINQ之类的可能性。

在C#中,LINQ有许多应用程序,但是我只想关注其中的一个(因为其他人已经可能被Kotlin语法覆盖):将SQL查询组合到远程数据库。

先决条件:

  • 我们有一个SQL数据库的数据模式以某种方式表示在代码中,以便静态工具(或类型系统)可以检查SQL查询的正确性(至少是命名)

  • 我们必须以字符串形式生成查询

  • 我们想要一个接近SQL或Java流的语法

问题 :表达式树增加了哪些对于手头任务至关重要的语法? 没有它们,SQL构建器可以有多好?

查询dsl可以在没有表达式树的情况下有多好?

正如JINQ所示,通过分析字节码来了解开发人员的意图,从而将谓词转换为SQL,您可以得到很大的帮助。 所以原则上表达式树并不是建立一个漂亮的查询dsl必不可少的:

val alices = database.customerStream().where { it.name == "Alice" } 

即使没有诸如字节码分析之类的骇人听闻,也可以通过代码生成来获得像样的查询dsl。 Querydsl和JOOQ就是很好的例子。 随着一点Kotlin包装代码,你可以写

 val alices = db.findAll(QCustomer.customer, { it.name.eq("Alice") }) 

表达式树如何帮助构建查询dsl

表达式树是代表解析为值的一些代码的结构。 通过编译器生成这样的结构,不需要字节码分析来理解应该做什么。 举个例子

 val alices = database.customerStream().where { it.name == "Alice" } 

where函数的参数是我们可以在运行时检查的expression ,并将其转换为SQL或其他查询语言。 由于表达式树表示代码,因此不需要在Kotlin和SQL范例之间切换来编写查询。 使用linq / jinq表达的查询代码看起来几乎相同,无论它们是在内存中使用POCO / POJO还是在数据库引擎中使用其查询语言执行。 编译器也可以做更多的类型检查。 此外,用内存表示替换底层数据库非常容易,可以使运行测试更快。

进一步阅读:

  • 什么是表达树,你如何使用它们,为什么要使用它们?
  • 表达树的实际使用

JOOQ和Querydsl:

ORM的典型解决方案是使用来自应用逻辑的DSL或嵌入式DSL。 尽管这些计划已经取得了巨大的进展,最终达到了JOOQ和Querydsl,但是这样一个系统仍然有许多需要注意的地方:

  • 许多写这些查询的人习惯于的范例(即类型安全)在关键方面缺失或不同
  • 确切的语义是不明显的:在前面的答案中,建议我们使用扩展方法eq来执行db本地相等过滤器。 很有可能一个新的开发者会错误地使用equals而不是eq
  • 第二点是由于测试的困难所致:使用带有假数据的活连接器是一个非常困难的问题,所以根据测试过程,不正确的代码jooqDB.where { it.name.equals("alice") }可能直到开发流水线的更远处才被发现。

Jinq

Jinq不是第一方数据连接器。 虽然我认为这一点的重要性在很大程度上是心身性的,但是重要的是重要的。 许多项目都将使用供应商建议的工具,所有主要的数据库供应商都有Java连接器,因此大多数开发人员可能会简单地使用它们。

尽管我没有使用Jinq,但我相信Jinq还没有看到广泛采用的另一个原因,主要是因为它试图用一个更加困难的领域来解决这个问题:从AST构建查询比从字节构建查询要容易得多代码与构建编译器后端的原因相比构建一个反编译器更容易。 虽然我忍不住要把自己的帽子给Jinq团队做出这样一个了不起的工作,但我也不禁想到他们被他们的工具妨碍了:用字节码构建查询是困难的。 根据定义,Java字节码被承诺在JVM上运行,试图改进对另一个解释器的承诺是一个非常困难的问题。

我目前的工作不允许我使用传统的数据库,但如果我要切换项目,知道我需要在DAL中暴露大量数据,我很可能会从Kotlin和Java撤回到.net,很大程度上因为Linq,而不是调查Jinq。 “来自Kotlin的Linq”可能会改变我的想法。

来自DB供应商的支持:

LINQ-to-SQL和LINQ-to-mongo数据库连接器已经在.net社区广泛采用。 这是因为他们是第一方的,高质量的,并且以合理简单的方式行事:编译AST到SQL(或者mongo-query-language)至少在概念上是直截了当的。 ORM的许多传统注意事项都适用,但是供应商(微软和Mongo)继续解决这些问题。

如果Kotlin支持类似于Linq的运行时代码树,并且Kotlin继续以当前的速度增长,那么我相信MongoDB和Hibernate团队可以快速地开始加装现有的LINQ-to-X连接器来支持Kotlin的客户,最终甚至像微软和IBM这样规模较大的公司也开始支持同样的流程。

来自Kotlin的Linq

更重要的是,Kotlin独特的“接收器类型”概念和inline的积极实现可能在Linq空间中发挥的确切作用是有趣的。 Linq-from-Kotlin可能比LINQ-from-C#更有效。

C#在哪里

 someInterface .where(customer -> customer.Name.StartsWith("A") && ComplexFunctionCallDoneByMongoDriver(customer)) .select(customer -> new ResultObject(customer.Name, customer.Age, OtherContext())) 

Kotlin可能能够取得进展:

 someInterface .filter { it.name startsWith "A" && inlinedComplexFunctionCallDoneOnDB(it) } //inlined methods would have their AST exposed -> can be run on the DB process instead of on the driver. .map { ResultObject(name, age, otherContext()) } //uses a reciever type, no need to specify "customer ->" //otherContext() might also be inlined 

这是我的头顶,我怀疑比我更聪明的大脑可以把这些工具更好地使用。

其他用途:

值得一提的是,关于运行时代码-AST的应用的假设是错误的:

其他[运行时AST问题域]已经被Kotlin语法所涵盖

我之所以提起这个问题,首先是因为我对Kotlin的无效安全特性和与Mockito的交互感到恼火:花了一些时间研究这个问题,没有为Kotlin设计的Mocking框架,只有可以是 Java框架的从Kotlin使用,有一些痛苦。

java域和Kotlin域中的一些当前未解决的问题:

  • 嘲笑框架,如上所述。 通过访问AST,所有关于Mockito所使用的参数操作顺序的聪明但奇怪的技巧已经过时了。 其他更传统的嘲笑框架获得更直观和类型安全的前端。
  • 用于UI框架或其他方式的绑定表达式通常会转换为字符串。 考虑一个UI框架,开发人员可以编写notifyOfUIElementChange { this.model.displayName }而不是notifyOfUIElementChange("model.displayName") 。 如果有人重新命名这个财产,那么后者就会遭受严重的陈旧问题的困扰。
    • 我很高兴看到ControlsFX家伙或Thomas Mikula可以用这样的功能做什么。
  • 类似于Kotlin特定的Linq:我怀疑Kotlin在这里的应用可能会提供一些我不知道的工具。 但是我确信他们确实存在。

我真的很喜欢Linq,我不禁想到,Kotlin专注于行业问题,一个来自Kotlin的Linq-Kotlin模块将是一个完美的组合,并使包括我在内的许多人的生活变得轻松一点。