21

我一直在这里查看查询表达式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# 与此完全不同,它有多种构建和组合谓词的方法

4

2 回答 2

32

对于需要显式引用的 F# 2.0 版本的查询,这很容易做到(我写了一篇关于它的博客文章)。有一种方法可以在 C#(another blog post)中实现类似的东西,我认为类似的技巧可以用 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放入查询的引文 (in 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")
于 2012-12-11T20:35:06.697 回答
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,托马斯的示例不需要特殊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/

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

于 2014-07-30T11:12:01.613 回答