如何在 F# 中编写查询表达式? [英] How do you compose query expressions in F#?

查看:19
本文介绍了如何在 F# 中编写查询表达式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我一直想知道为什么以下内容是合法的

让 testQuery = 查询 {为 netflix.Titles 中的数字做where (number.Name.Contains("Test"))}

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

let ChristmasPredicate = fun (x:Catalog.ServiceTypes.Title) ->x.Name.Contains("圣诞节")让 testQuery = 查询 {为 netflix.Titles 中的数字做其中圣诞节谓词}

当然,F# 允许这样的可组合性,因此您可以重用谓词??如果我想要圣诞节标题与另一个谓词相结合,比如在特定日期之前怎么办?我必须复制并粘贴我的整个查询?C# 与此完全不同,它有多种构建和组合谓词的方法

解决方案

这对于需要显式引用的 F# 2.0 版本的查询非常容易(我写了一个 关于它的博文).有一种方法可以在 C# 中实现类似的东西(另一个博客文章),我认为类似的技巧可能是玩过 F# 3.0.

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

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

其中 <@ .. @> 中的代码是引用的 F# 代码 - 也就是说,代码存储在表示源代码的 Expr 类型中,并且可以转换为 LINQ 表达式,从而转换为 SQL.

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

let db = Nwind.GetDataContext()让谓词 = <@ fun (p:Nwind.ServiceTypes.Products) ->p.UnitPrice.Value >50.0M @>让测试 () =<@query.Select( query.Where(query.Source(db.Products), %predicate),有趣的 p ->p.ProductName) @>|>查询.运行|>Seq.iter (printfn "%s")

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

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

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

打开 System.Linq打开 Microsoft.FSharp.Quotations打开 Microsoft.FSharp.Quotations.Patterns让 runQuery (q:Expr>) =匹配 q|应用程序(Lambda(builder, Call(Some builder2, miRun, [Quote body])), queryObj) ->query.Run(Expr.Cast>(body))|_ ->以错误的论点"失败让测试 () =<@ query { for p in db.Products do其中 ((%谓词) p)选择 p.ProductName } @>|>运行查询|>Seq.iter (printfn "%s")

I've been looking at query expressions here http://msdn.microsoft.com/en-us/library/vstudio/hh225374.aspx

And I've been wondering why the following is legitimate

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

But you can't really do something like this

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

Surely F# allows composability like this so you can reuse a predicate?? What if I wanted Christmas titles combined with another predicate like before a specific date? I have to copy and paste my entire query? C# is completely unlike this and has several ways to build and combine predicates

解决方案

This was quite easy to do with the F# 2.0 version of queries which required explicit quotations (I wrote a blog post about it). There is a way to achieve similar thing in C# (another blog post) and I think similar tricks could be played with F# 3.0.

If you do not mind uglier syntax, then you can use explicit quotations in F# 3.0 too. When you write
query { .. } the compiler actually generates something like:

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

where the code inside <@ .. @> is quoted F# code - that is, code stored in an Expr type that represents the source code and can be translated to LINQ expressions and thus to SQL.

Here is an example that I tested with the SqlDataConnection type provider:

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

The key trick is that, when you use explicit quotations (using <@ .. @>) you can use the % operator for quotation slicing. This means that the quotation of predicate is put into the quotation of the query (in test) in place where you write %predicate.

The code is quite ugly compared to the nice query expression, but I suspect you could make it nicer by writing some DSL on top of this or by pre-processing the quotation.

EDIT: With a bit more effort, it is actually possible to use the query { .. } syntax again. You can quote the entire query expression and write <@ query { .. } @> - this will not directly work, but you can then take the quotation and extract the actual body of the query and pass it to query.Run directly. Here is a sample that works for the above example:

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

这篇关于如何在 F# 中编写查询表达式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆