2010-04-26 6 views
3

Quand vous faites LINQ to SQL en C#, vous pouvez faire quelque chose comme ceci:Composable FLinq expressions

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

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

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

Cela générerait 2 requêtes SQL séparées, une avec TOP 10, et l'autre avec les 3 meilleurs. En jouant avec Flinq, je vois que:

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 

ne fait pas la même chose. Ici, il semble faire une requête complète, puis faire les appels "prendre" sur le côté client. Une alternative que je vois utilisée est:

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 @> 

Ces 2 génèrent le SQL avec le filtre TOP N approprié. Mon problème avec ceci est que cela ne semble pas composable. Je dois essentiellement dupliquer la clause "where", et éventuellement dupliquer d'autres sous-requêtes que je pourrais vouloir exécuter sur une requête de base. Y a-t-il un moyen de faire que F # me donne quelque chose de plus composable?

(je l'origine posted this question to hubfs, où je l'ai obtenu quelques réponses, portant sur le fait que C# effectue la transformation de requête « à la fin », à savoir lorsque les données sont nécessaires, où F # fait que la transformation avec impatience.)

Répondre

5

Pour cela en F #, vous devrez utiliser une approche légèrement différente. Au lieu de composer la requête en utilisant des appels de méthode (et une exécution différée), vous devrez construire le code F # cité. S'il vous suffit de paramétrer le code par un argument numérique, vous pouvez écrire une fonction qui exécute la requête:

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

Cela ne fonctionne que dans des cas simples, car le paramètre peut être un nombre. Cependant, vous pouvez également composer des citations F # d'une manière qui vous permet d'ajouter d'autres opérations à une requête principale. Par exemple, supposons que vous souhaitiez ajouter Seq.take ou Seq.sortBy à la partie principale de la requête. Cela peut être fait en utilisant ce que l'on appelle l'opérateur d'épissage . citations à l'intérieur (code à l'intérieur <@ .. @>), vous pouvez utiliser un operato spécial % que vous permet de épissure une autre citation dans celui que vous construisez:

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

Ici, le paramètre op est de type Expr<seq<MyTableRow> -> 'a>. Vous pouvez appeler la fonction createQuery avec un guillemet comme argument et ajouter l'argument après la requête principale. Par exemple:

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

Ceci est en réalité beaucoup plus puissant que ce que C# vous permet de faire. J'ai écrit deux articles sur ce il y a quelque temps:

  • montre quelques exemples F # - malheureusement, il utilise la syntaxe obsolète, il ne fonctionnera donc pas directement, mais il doit démontrer les idées. Dans les anciennes versions F #, vous pouvez utiliser _ dans la citation qui a automatiquement créé une fonction qui a pris la citation (à épisser à la place de _ comme argument).Cela devrait être réécrite en utilisant (vous pouvez aussi ne pas besoin de drôles de personnages Unicode plus :-)):

    (fun x -> <@ .. %x .. @>) 
    
  • Composing LINQ queries at runtime in C# montre comment fournir des fonctionnalités supplémentaires qui ne sont pas directement disponibles en C# (en utilisant quelques astuces)