【问题标题】:How do you compose query expressions in F#?如何在 F# 中编写查询表达式?
【发布时间】:2012-12-11 19:02:45
【问题描述】:

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

【问题讨论】:

  • C#完全是like这个,你不能where predicate。你可以做Where(predicate),但我认为你也可以在这里做同样的事情。
  • 我从未说过。但据我目前所见,您不能在查询表达式中执行此操作。我也有同样的想法,但似乎无法编译任何东西。互联网上也没有关于此的文献。另外,哪里只能取布尔值,而不是表达式,所以我认为您错了:) 证明吗? where(expression) 很好,因为有像 LinqKit 这样的东西可以让你疯狂地操纵你的表达式。
  • 我的 F# 3 交互有问题,但 where (christmasPredicate number) 不起作用?
  • 不错的拉蒙;这使得我可以编译它,但显然查询表达式的翻译方式不允许翻译查询并引发异常。我不会放弃,因为我们似乎正在取得进展。在 ORM 方面,表达式树似乎比计算表达式更灵活,但就像我说的那样,我可能完全错了。
  • 我没想到会这样。我的意见是 LINQ to Entities 绝对可以在 where 子句中采用任意 Expression (可能由其他表达式构建),只要函数调用 INSIDE 表达式是可翻译的,并且它使其更加灵活和可组合。

标签: f# computation-expression query-expressions


【解决方案1】:

使用需要显式引用的 F# 2.0 版本的查询很容易做到这一点(我写了一个blog post about it)。有一种方法可以在 C# 中实现类似的功能(另一个 blog post),我认为 F# 3.0 可以发挥类似的技巧。

如果您不介意更丑陋的语法,那么您也可以在 F# 3.0 中使用显式引用。当您编写
query { .. } 时,编译器实际上会生成如下内容:

query.Run(<@ ... @>)

&lt;@ .. @&gt; 中的代码被引用 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")

关键技巧是,当您使用显式引用(使用&lt;@ .. @&gt;)时,您可以使用% 运算符进行引用切片。这意味着predicate 的引用被放入查询的引用(在test)中,在你写%predicate 的地方。

与漂亮的查询表达式相比,该代码相当难看,但我怀疑您可以通过在此基础上编写一些 DSL 或预处理引用来使其更好。

编辑:稍加努力,实际上可以再次使用query { .. } 语法。您可以引用整个查询表达式并编写&lt;@ query { .. } @&gt; - 这不会直接起作用,但您可以获取引用并提取查询的实际正文并将其直接传递给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")

【讨论】:

  • 你是巫师吗?做得很好。这对我来说将是一个交易破坏者。我很惊讶这个问题没有经常出现。
  • 您实际上可以直接拼接到查询中,但我不确定这是功能还是错误...
  • @kvb 使用直接拼接时出现运行时错误(因为它实际上并没有拼接,而是调用运算符)。
  • @TomasPetricek 很棒的代码,已经搜索了好几天了,非常感谢!请参阅我的问题:stackoverflow.com/questions/16800714/…。你可以得到赏金。
【解决方案2】:

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

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# 查询表达式的重要子集生成单个查询提供了强有力的保证。使用FSharpComposableQueryquery 版本,上述幼稚的组合工作。我们还进行了广泛的测试,以确保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/

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-22
    • 2011-05-20
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    相关资源
    最近更新 更多