11

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

据我了解,LINQ 可以“累积”各种操作,并且可以在查询到数据库时将“整个”语句提供给数据库,从而防止简单的SELECT第一次将整个表复制到 VM 的数据结构中。

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

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

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

还是我的观察完全错误?

4

5 回答 5

13

作为 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 < 5:Int”实际上是“u.id:Column[Int] < u.id:Column[Int]”。运行此表达式会生成查询 AST,例如 Operator.Relational("<", NamedColumn("user", "id"), ConstColumn(5))。类似地,Query monad 的“filter”和“map”方法实际上并不执行过滤和映射,而是建立一个描述这些操作的 AST。

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

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

于 2010-12-06T21:41:35.153 回答
13

我应该提一下,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 在 Scala Days 2010 上提出。

于 2010-12-06T21:57:08.490 回答
5

使用 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。

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

于 2010-12-06T12:10:04.530 回答
4

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

于 2010-12-06T12:10:41.203 回答
1

查看http://squeryl.org/

于 2010-12-06T21:48:58.583 回答