可组合的FLinq表达式

时间:2010-04-26 17:11:08

标签: linq-to-sql f#

在c#中执行linq-to-sql时,你可以这样做:

var data = context.MyTable.Where(x => x.Parameter > 10); 

var q1 = data.Take(10); 
var q2 = data.Take(3); 

q1.ToArray(); 
q2.ToArray(); 

这会产生2个单独的SQL查询,一个是TOP 10,另一个是TOP 3.在玩Flinq时,我看到了:

let data = query <@ seq { for i in context.MyTable do if x.Parameter > 10 then yield i } @> 

data |> Seq.take 10 |> Seq.toList 
data |> Seq.take 3 |> Seq.toList 

没有做同样的事情。这里似乎做了一个完整的查询,然后在客户端进行“接听”调用。我看到的替代方案是:

let q1 = query <@ for i in context.MyTable do if x.Param > 10 then yield i } |> Seq.take 10 @> 
let q2 = query <@ for i in context.MyTable do if x.Param > 10 then yield i } |> Seq.take 3 @> 

这两个用适当的TOP N过滤器生成SQL。我的问题是它似乎不可组合。我基本上必须复制“where”子句,并且可能必须复制我可能想要在基本查询上运行的其他其他子查询。有没有办法让F#给我一些更可组合的东西?

(我最初posted this question to hubfs,我得到了一些答案,处理了C#在“结束时”执行查询转换的事实,即当需要数据时,F#正在急切地进行转换。)

1 个答案:

答案 0 :(得分:5)

要在F#中执行此操作,您需要使用稍微不同的方法。您需要构造引用的F#代码,而不是使用方法调用(和延迟执行)来组成查询。如果您只需要通过某个数字参数来参数化代码,则可以编写一个运行查询的函数:

let takeData count = 
  <@ seq { for i in context.MyTable do 
             if x.Parameter > 10 then 
               yield i }
     |> Seq.take count @> |> query

这仅适用于简单情况,因为参数只能是一个数字。但是,您也可以以允许您向核心查询添加其他操作的方式编写F#引用。

例如,假设您要将Seq.takeSeq.sortBy附加到查询的核心部分。这可以使用所谓的拼接运算符来完成。在引号内(<@ .. @>内的代码),您可以使用特殊的操作符%,它允许您拼接另一个引用到您正在构建的引号中:

let createQuery op = 
  <@ seq { for i in context.MyTable do 
             if x.Parameter > 10 then 
               yield i }
     |> %op @> |> query

此处,op参数的类型为Expr<seq<MyTableRow> -> 'a>。您可以使用某个引号作为参数调用createQuery函数,它将在核心查询之后附加参数。例如:

createQuery <@ Seq.take 10 @>
createQuery <@ Seq.sortBy (fun x -> x.Parameter) @>

这实际上比C#允许你做的更强大。我前段时间写了两篇关于此的文章:

  • Composing LINQ queries at runtime in F#显示了一些F#示例 - 遗憾的是,它使用了过时的语法,所以它不会直接起作用,但它应该展示这些想法。在旧版本的F#中,您可以在引号中使用_,它会自动创建一个带引号的函数(拼接代替_作为参数)。这将不得不重写(你也不再需要有趣的Unicode字符: - )):

    (fun x -> <@ .. %x .. @>)
    
  • Composing LINQ queries at runtime in C#展示了如何提供一些在C#中无法直接使用的其他功能(使用一些技巧)