如何在 F# 中编写查询表达式? [英] How do you compose query expressions in 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屋!