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



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

这在F#2.0版本的查询中很容易做到,因为它需要显式引用(我写了一篇关于它的博客文章)。在C#(另一篇博客文章)中有一种方法可以实现类似的事情,我认为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的引号被放入查询的引号中(在test中),而不是写入%predicate的位置。

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

EDIT:只要多花一点力气,实际上就可以再次使用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")

在最初的示例中,可以尝试引用谓词,然后将其拼接到:

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,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/

[编辑:由于项目名称已更改,更改了上述链接以删除"实验"一词。]

相关内容

  • 没有找到相关文章

最新更新