2010-12-06 66 views
11

据我了解,LINQ支持的唯一东西,即Scala目前不与其集合库,是与SQL数据库的集成。Scala类似于SQL的支持,如LINQ

据我所知,LINQ可以“积累”各种操作,并可以在查询数据库时向数据库提供“全部”语句,以防止简单的SELECT首先将整个表复制到VM的数​​据结构中。

如果我错了,我会很乐意被纠正。

如果没有,在Scala中支持相同的功能有什么必要?

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

还是我完全错了我的观察?

+0

非常好的问题被使用。作为一个对函数式语言感兴趣的人,我很好奇Scala在这方面提供了什么,以及如何/如何将LINQ样式的支持作为库来构建。请提出这个问题。 – Stilgar 2010-12-06 12:17:36

+0

我知道,Scala的团队认为有关集成像LINQ,但我想这还需要一段时间... – Landei 2010-12-06 15:22:32

+0

UPDATE:这些天你应该看看斯利克 - https://github.com/slick – Jack 2012-07-16 09:54:26

回答

13

作为ScalaQuery的作者,我没有太多补充到斯蒂尔加的解释。在Scala中缺少的LINQ部分确实是表达式树。这就是为什么ScalaQuery对Column和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现在类型列[INT]和柱[字符串] 分别。所有计算都是在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) 

过滤器和映射方法没有检查他们的论据是表达式树以构建查询,他们只是运行它们。正如你可以从这些类型中看到的那样,表面上看起来像“u.id:Int < 5:Int”实际上是“u.id:Column[Int] < u.id:Column[Int]”。运行此表达式会产生查询AST,如Operator.Relational(“<”,NamedColumn(“user”,“id”),ConstColumn(5))。同样,查询单元的“过滤器”和“映射”方法实际上并不执行过滤和映射,而是建立一个描述这些操作的AST。

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

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

5

使用LINQ,编译器检查是否将lambda表达式编译为IEnumerable或IQueryable。第一部作品如斯卡拉收藏。第二个将表达式编译成表达式树(即数据结构)。 LINQ的强大之处在于编译器本身可以将lambda表达式转换为表达式树。你可以编写一个库来构建表达式树,其接口类似于你收集的接口,但是如何让编译器从lambdas构建数据结构(而不​​是JVM代码)呢?

这就是说我不确定Scala在这方面提供了什么。也许可以用Scala中的lambdas构建数据结构,但无论如何我相信在编译器中需要类似的功能来构建对数据库的支持。请注意,数据库不是您可以构建提供程序的唯一基础数据源。例如,有很多LINQ提供商可以像Active Directory或Ebay API那样提供内容。

编辑: 为什么不能只有一个API?为了使查询不仅使用API​​方法(filter,Where等...),而且还使用lambda表达式作为这些方法的参数。其中(x => x> 3)(C# LINQ)。编译器将lambda转换为字节码。 API需要构建数据结构(表达式树),以便将数据结构转换为基础数据源。基本上你需要编译器为你做这个。

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

声明2: 也许Scala语言实际上可以提供lambda作为可检查的对象,以便您可以检索表达式树。这将使Scala中的lambda功能等同于C#中的lambda功能。也许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。没有这个选项,我们必须使用表格和列这样的元对象来构建我们自己的AST。

确实很酷的库。我希望在未来得到应有的爱,成为真正的生产准备工具。

+0

不会吧足以拥有一个API,它只是组装查询字符串并使用它来查询数据库? – soc 2010-12-06 12:28:01

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))))))) 

这已在字节码生成库“口诀”,这是由它的作者约翰·鲁道夫presented在斯卡拉日2010年