你如何在F#中编写查询表达式?

23

我一直在看这里的查询表达式 http://msdn.microsoft.com/en-us/library/vstudio/hh225374.aspx

而我一直在想为什么以下内容是合法的

let testQuery = query {
        for number in netflix.Titles do
        where (number.Name.Contains("Test"))
    }

但你真的做不到这样的事情

let christmasPredicate = fun (x:Catalog.ServiceTypes.Title) -> x.Name.Contains("Christmas")
let testQuery = query {
        for number in netflix.Titles do
        where christmasPredicate 
    }

F# 肯定允许这样的可组合性,你可以重用一个谓词?? 如果我想将圣诞节标题与另一个谓词(例如在特定日期之前)组合起来怎么办?我必须复制并粘贴整个查询吗?不同于这一点,C# 有几种方法来构建和组合谓词


2
C# 就是这样的,你不能使用 where predicate。你可以使用 Where(predicate),但我认为在这里也可以做同样的事情。 - svick
我从未说过其他的话。但是从目前我所看到的情况来看,你无法在查询表达式中完成这个操作。我也曾这样想过,但似乎无法编译任何东西。此外,互联网上也没有相关文献。而且,where只能接受bool类型,而不是表达式,所以我认为你是错的 :) 证明一下?where(expression<predicate>)很好,因为有像LinqKit这样的工具可以让你对表达式进行各种操作。 - Brian Rosamilia
1
我的 F# 3 交互式出了问题,但是 where (christmasPredicate number) 行不行? - Ramon Snir
不错,Ramon。这样我就可以编译它了,但显然查询表达式的翻译方式不允许查询被翻译并抛出异常。我还不会放弃,因为我们似乎正在取得进展。看起来表达式树在ORM方面比计算表达式更灵活,但像我说的,我可能完全错了。 - Brian Rosamilia
我不确定它能将您的通用函数翻译成SQL,这是真的。我相信查询中有一些魔法(但由于缺乏兴趣,我没有深入挖掘F# 3内部)。 - Ramon Snir
2
我并没有期望它做到这点。我的问题在于,只要表达式内部的函数调用是可翻译的,LINQ to Entities绝对可以在where子句中使用任意Expression<predicate>(可能由其他表达式构建),这使其更具灵活性和可组合性。 - Brian Rosamilia
2个回答

34
这个可以用 F# 2.0 版本的查询来完成,需要显式引用(我写了一篇 博客文章)。在 C# 中也有类似的方法(另一篇博客文章),我认为在 F# 3.0 中也可以使用类似的技巧。
如果您不介意语法更丑陋,那么您也可以在 F# 3.0 中使用显式引用。当您编写 query { .. } 时,编译器实际上会生成类似于:
query.Run(<@ ... @>)

<@ .. @> 中的代码是 F# 代码 - 即,存储在 Expr 类型中的代码,该类型表示源代码并可以转换为 LINQ 表达式,从而转换为 SQL。

这是我使用 SqlDataConnection 类型提供程序测试的示例:

let db = Nwind.GetDataContext()

let predicate = <@ fun (p:Nwind.ServiceTypes.Products) -> 
  p.UnitPrice.Value > 50.0M @>

let test () =
  <@ query.Select
      ( query.Where(query.Source(db.Products), %predicate), 
        fun p -> p.ProductName) @>
  |> query.Run
  |> Seq.iter (printfn "%s")

关键技巧在于,当您使用显式引用(使用<@ .. @>)时,您可以使用%运算符进行引用切片。这意味着将predicate的引用放入查询的引用中(在test中),您可以在写入%predicate的位置上使用它。

与漂亮的查询表达式相比,代码相当丑陋,但我怀疑通过在此之上编写一些DSL或预处理引用,您可以使其更加美观。

编辑:经过更多的努力,实际上可以再次使用query { .. }语法。您可以引用整个查询表达式并编写<@ query { .. } @> - 这不会直接起作用,但是然后您可以获取引用并提取查询的实际主体,并将其直接传递给query.Run。以下是适用于上述示例的示例:

open System.Linq
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns

let runQuery (q:Expr<IQueryable<'T>>) = 
  match q with
  | Application(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->
      query.Run(Expr.Cast<Microsoft.FSharp.Linq.QuerySource<'T, IQueryable>>(body))
  | _ -> failwith "Wrong argument"

let test () =
  <@ query { for p in db.Products do
             where ((%predicate) p)
             select p.ProductName } @>
  |> runQuery
  |> Seq.iter (printfn "%s")

2
你是巫师吗? 做得非常好。 这对我来说将是一项决定性的因素。 我很惊讶为什么这个问题没有更经常出现。 - Brian Rosamilia
你实际上可以直接将splice插入到查询中,但我不确定这是一个特性还是一个错误... - kvb
@kvb 当我使用直接拼接时出现运行时错误(因为它实际上并没有进行拼接,而是调用了操作符)。 - Ramon Snir
@TomasPetricek 很棒的代码,我已经搜索了几天了,非常感谢!请看我的问题:https://dev59.com/zXLYa4cB1Zd3GeqPaKbF#16950567。你可以得到赏金。 - halcwb

6

在最初的例子中,可能会尝试引用谓词,然后将其拼接进去:

let christmasPredicate = <@ fun (x:Catalog.ServiceTypes.Title) -> 
                             x.Name.Contains("Christmas") @>
let testQuery = query {
        for number in netflix.Titles do
        where ((%christmasPredicate) number) 
        select number
    }

(我稍微修改了原始示例)例如这样的示例(具有简单的一阶lambda抽象)通常在F#中仍然有效,但是一般来说,F#的默认QueryBuilder不能保证规范化引用术语中的lambda抽象的结果应用。这可能导致奇怪的错误消息或性能差的查询(例如,在第一个表上查询,然后为第一个表的每一行生成另一个表上的一个查询,而不是执行单个查询连接)。
我们最近开发了一个名为FSharpComposableQuery的库(如果打开),它会重载query运算符以执行规范化(并执行其他有用的操作)。它提供了一个强有力的保证,为非平凡的F#查询表达式子集生成单个查询。使用FSharpComposableQuery的query版本,上述天真的组合起作用。我们还进行了广泛的测试,以确保FSharpComposableQuery不会破坏现有的查询代码。
同样地,例如,使用FSharpComposableQuery,Tomas的示例不需要特殊的RunQuery函数。相反,可以简单地执行:
open FSharpComposableQuery

let predicate = <@ fun (p:Nwind.ServiceTypes.Product) -> 
                     p.UnitPrice.Value > 50.0M @>
let test () =
  query { for p in db.Products do
          where ((%predicate) p)
          select p.ProductName }
  |> Seq.iter (printfn "%s")

(注意:我只测试了Northwind的OData版本,而不是SQL类型提供程序的上述代码,但我们已经测试了许多类似且更复杂的示例。OData版本会出现来自OData的神秘错误,但这似乎与手头的问题无关。) FSharpComposableQuery现在可以从NuGet下载,链接如下:https://www.nuget.org/packages/FSharpComposableQuery 更多信息(包括示例和一个小教程,演示更复杂的组合形式)可以在此处找到:

http://fsprojects.github.io/FSharp.Linq.ComposableQuery/

[编辑:更改了上述链接,删除了“实验性”一词,因为项目名称已更改。]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接