你如何在F#中组成查询表达式?

时间:2012-12-11 19:02:46

标签: f# computation-expression query-expressions

我一直在查看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 个答案:

答案 0 :(得分:29)

这对于需要显式引用的F#2.0版本的查询非常容易(我写了blog post about it)。有一种方法可以在C#(另一个blog post)中实现类似的功能,我认为可以用F#3.0播放类似的技巧。

如果你不介意uglier语法,那么你也可以在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")

答案 1 :(得分:4)

天真地,在原始示例中,可以尝试引用谓词,然后将其拼接:

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的{​​{1}}版本,上述天真的构图有效。我们还进行了广泛测试,以确保query不会破坏现有的查询代码。

同样,例如,使用FSharpComposableQuery,Tomas的示例不需要特殊的FSharpComposableQuery功能。相反,人们可以做到:

RunQuery

(警告:我之前仅使用Northwind的OData版本测试了上述代码,而不是SQL类型提供程序,但是我们测试了大量类似且更复杂的示例.OData版本失败,出现了一个神秘的错误OData,但这似乎与手头的问题正交。)

现在可以从NuGet获取

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") https://www.nuget.org/packages/FSharpComposableQuery

可以在这里找到更多信息(包括示例和一个小教程,演示更复杂的构图形式):

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

[编辑:更改了上述链接以删除单词&#34; Experimental&#34;,因为项目名称已更改。]