我一直在这里查看查询表达式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/
[编辑:由于项目名称已更改,更改了上述链接以删除"实验"一词。]