【问题标题】:Scala collection-like SQL support as in LINQ与 LINQ 中的类似 Scala 集合的 SQL 支持
【发布时间】:2010-12-06 11:58:10
【问题描述】:

据我了解,LINQ 唯一支持(Scala 目前不使用其集合库)是与 SQL 数据库的集成。

据我了解,LINQ 可以“累积”各种操作,并且可以在查询到数据库时将“整个”语句提供给数据库,从而防止简单的 SELECT 首先将整个表复制到虚拟机。

如果我错了,我很乐意得到纠正。

如果不是,在 Scala 中需要什么来支持相同的功能?

难道不能编写一个实现集合接口的库,但没有任何数据结构支持它,而是一个字符串,它与以下集合组装到所需的数据库语句中?

或者我的观察完全错了?

【问题讨论】:

  • 非常好的问题。作为一个对函数式语言感兴趣的人,我真的很好奇 Scala 在这方面提供了什么以及是否/如何将 LINQ 风格的支持构建为一个库。请为这个问题投票。
  • 我知道 Scala 团队正在考虑集成 LINQ 之类的东西,但我想这需要一些时间......
  • 更新:这些天你应该看看 Slick - github.com/slick

标签: c# database linq scala scalaquery


【解决方案1】:

作为 ScalaQuery 的作者,我对 Stilgar 的解释没有什么要补充的。 Scala 中缺少的 LINQ 部分确实是表达式树。这就是为什么 ScalaQuery 对 Column 和 Table 类型而不是这些实体的基本类型执行所有计算的原因。

您将表声明为具有其列的投影(元组)的 Table 对象,例如:

class User extends Table[(Int, String)] {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def name = column[String]("name")
  def * = id ~ name
}

User.id 和 User.name 现在分别是 Column[Int] 和 Column[String] 类型。所有计算都在 Query monad 中执行(这是数据库查询的更自然的表示,而不是必须从它创建的 SQL 语句)。进行以下查询:

val q = for(u <- User if u.id < 5) yield u.name

经过一些隐式转换和脱糖后,这将转化为:

val q:Query[String] =
  Query[User.type](User).filter(u => u.id < ConstColumn[Int](5)).map(u => u.name)

filter 和 map 方法不必检查它们的参数作为表达式树来构建查询,它们只是运行它们。从类型中可以看出,表面上看起来像“u.id:Int

QueryBuilder 然后使用这个 AST 为数据库构造实际的 SQL 语句(使用 DBMS 特定的语法)。

ScalaQL 采用了另一种方法,它使用编译器插件直接处理表达式树,确保它们只包含数据库查询中允许的语言子集,并静态构造查询。

【讨论】:

  • 嗨,赛格!感谢您的回答。所以基本上 ScalaQuery 做了我的想法?该语句通过各种方法调用在内部构建,并作为一个整体提供给数据库。那是对的吗?不再需要编写类声明需要什么?一些反射魔法可以自动创建def *,但如果所有内容都被删除,是否有必要在编译时设置正确的数据库,以便电子编译器可以正确推断类型?
  • 没错。您可以更进一步,定义一个 QueryTemplate,它只编译一次为 SQL 字符串并作为 PreparedStatement 发送到 DBMS,以便在使用不同参数重复调用它时实现高效重用。反射魔法当然可以用于从(案例)类创建表并构建查询,但查询 DSL 必须是无类型的。如果你想要静态类型,编译器需要 Table 对象(可以在构建过程中自动生成)。或者您使用 ScalaQL 之类的编译器插件编译真正的 Collection API 调用以进行查询。
【解决方案2】:

我应该提一下,Scala 确实为表达式树提供了实验性支持。如果您将匿名函数作为参数传递给需要 scala.reflect.Code[A] 类型参数的方法,您会得到一个 AST。

scala> import scala.reflect.Code      
import scala.reflect.Code 
scala> def codeOf[A](code: Code[A]) = code
codeOf: [A](code:scala.reflect.Code[A])scala.reflect.Code[A]
scala> codeOf((x: Int) => x * x).tree 
res8: scala.reflect.Tree=Function(List(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),Apply(Select(Ident(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),Method(scala.Int.$times,MethodType(List(LocalValue(NoSymbol,x$1,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))),PrefixedType(ThisType(Class(scala)),Class(scala.Int))))),List(Ident(LocalValue(NoSymbol,x,PrefixedType(ThisType(Class(scala)),Class(scala.Int)))))))

这已在字节码生成库“助记符”中使用,该库的作者 Johannes Rudolph 在 2010 年 Scala Days 上使用了 presented

【讨论】:

    【解决方案3】:

    使用 LINQ,编译器会检查 lambda 表达式是编译为 IEnumerable 还是 IQueryable。第一个像 Scala 集合一样工作。第二个将表达式编译为表达式树(即数据结构)。 LINQ 的强大之处在于编译器本身可以将 lambda 转换为表达式树。您可以编写一个库来构建表达式树,其接口类似于您用于收集的接口,但是您将如何让编译器从 lambdas 构建数据结构(而不​​是 JVM 代码)?

    话虽如此,我不确定 Scala 在这方面提供了什么。也许可以在 Scala 中使用 lambda 构建数据结构,但无论如何我相信您需要在编译器中使用类似的功能来构建对数据库的支持。请注意,数据库不是您可以为其构建提供程序的唯一基础数据源。有许多 LINQ 提供程序可以提供诸如 Active Directory 或 Ebay API 之类的东西。

    编辑: 为什么不能只有一个 API?

    为了进行查询,您不仅要使用 API 方法(过滤器、Where 等),还要使用 lambda 表达式作为这些方法的参数。Where(x => x > 3) (C# LINQ )。编译器将 lambda 转换为字节码。 API 需要构建数据结构(表达式树),以便您可以将数据结构转换为底层数据源。基本上你需要编译器为你做这件事。

    免责声明 1: 也许(只是也许)有一些方法可以创建执行 lambdas 但重载运算符以生成数据结构的代理对象。这将导致比实际 LINQ(运行时与编译时)稍差的性能。我不确定这样的图书馆是否可能。也许 ScalaQuery 库使用类似的方法。

    免责声明 2: 也许 Scala 语言实际上可以将 lambdas 提供为可检查的对象,以便您可以检索表达式树。这将使 Scala 中的 lambda 特性等同于 C# 中的特性。也许 ScalaQuery 库使用了这个假设的特性。

    编辑 2: 我做了一点挖掘。似乎 ScalaQuery 使用库方法并重载了一堆运算符以在运行时生成树。我不完全确定细节,因为我不熟悉 Scala 术语并且很难阅读文章中复杂的 Scala 代码: http://szeiger.de/blog/2008/12/21/a-type-safe-database-query-dsl-for-scala/

    就像每个可以在查询中使用或从查询返回的对象一样,表是用它所代表的值的类型进行参数化的。这始终是单个列类型的元组,在我们的例子中是整数和字符串(注意使用 java.lang.Integer 而不是 Int;稍后会详细介绍)。在这方面,SQuery(正如我现在命名的那样)更接近 HaskellDB 而不是 LINQ,因为 Scala(像大多数语言一样)不允许您在运行时访问表达式的 AST。在 LINQ 中,您可以使用数据库中值和列的真实类型编写查询,并在运行时将查询表达式的 AST 转换为 SQL。如果没有这个选项,我们必须使用 Table 和 Column 等元对象来构建我们自己的 AST。

    确实很酷的图书馆。我希望在未来它得到应有的爱,并成为真正的生产就绪工具。

    【讨论】:

    • 拥有一个仅组装查询字符串并使用它来查询数据库的 API 还不够吗?
    • @Stilgar:微软前段时间难道没有弃用 LINQ-to-SQL 的东西吗?我记得他们在第一次发布之前就取消了支持其他数据库供应商的可能性,只允许 MS SQL ...
    • LINQ to SQL 没有被弃用,但不再是开发的主要焦点,尽管它们仍然添加了优化和一些小功能。实体框架是。我个人认为 LINQ to SQL 只是一个生产就绪的概念证明。这是一个很酷的概念证明,许多人喜欢它的简单性。然而,在这个问题的上下文中,提供者并不重要,无论是 LINQ to SQL、EF、Active Directory 等。重要的是有一个表达式树。您可以编写代码以任何您喜欢的方式翻译这棵树。 BTW StackOverflow 运行 ot LINQ to SQL。
    • “[...] 您不仅使用 API 方法(过滤器、Where 等...),还使用 ​​lambda 表达式作为这些方法的参数。”嗯,我想我错过了这里的重点......
    • 您可以运行作为参数获得的 lambda,但您无法使用自己的代码检查其 CODE,以便您可以将该 lambda 代码转换为 SQL。这就是为什么它不能像在 C# 中那样在 Scala 中完成。看起来您可以将 lambdas 包装在某种单子中并查询可以生成 SQL 的代理对象。我不完全理解它是如何完成的,但显然是可能的。请注意,表类对字段使用包装器 (Column) 而不是 Integer。这些类以某种方式将 lambda 转换为 SQL 代码。
    【解决方案4】:

    您可能想要http://scalaquery.org/ 之类的东西。它完全符合@Stilgar 的回答,除了它只是 SQL。

    【讨论】:

    • 非常有趣。他们如何将 lambda 转换为表达式树?
    • 我知道 scalaquery,但我想知道是否可以将查询集成到集合 API 中,而无需像我们拥有的数据库查询 API 那样完全不同的 API。
    • 此外,在 Scalaquery 中,您必须再次在代码中描述您的数据库表,因为在 LINQ 中这不是必需的。
    • 这在 LINQ to SQL 中不是必需的,因为它有一个代码生成器可以为您完成。可以为 ScalaQuery 编写这样的代码生成器,如果 ScalaQuery 变得流行,那么有人会这样做。从理论上讲,您可以为查询添加一定程度的动态性,甚至避免代码生成,但是您将失去一些类型安全性。顺便说一句,F# 团队对名为 Type Providers 的东西进行了非常酷的研究,旨在取代这种情况和类似情况下的代码生成,但这是完全不同的讨论。
    • @soc Scalaquery 使用flatMapmapforeach,就像集合一样,所以它应该在理解级别上有一些集成。您想到了什么样的集成?
    【解决方案5】:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-16
      • 1970-01-01
      • 2019-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-08
      • 1970-01-01
      相关资源
      最近更新 更多