38 votes

groupby de plusieurs colonnes dans une requête F# 3.0

Je suis en train d'essayer F# 3.0 et je me heurte à un mur lorsqu'il s'agit de regrouper par plusieurs colonnes. La chose évidente à essayer était

query {
    for d in context.table do
    groupBy (d.col1,d.col2) into g
    select (g.Key)
}

Mais je reçois une exception "Only parameterless constructors and initializers are supported in LINQ to Entities".

Je n'arrive pas à trouver un exemple sur msdn.

http://msdn.microsoft.com/en-us/library/hh225374(v=vs.110).aspx

http://msdn.microsoft.com/en-us/library/hh361035(v=vs.110).aspx

Et je réalise que ma question est similaire à " Entity Framework et types anonymes en F# "mais il semble que ce problème soit lié à powerpack/F#2.x et j'espère que F# 3.0 a une réponse élégante... Des idées ?

UPDATE :

J'ai découvert l'attribut CLIMutable en lisant le billet de Brian sur le site :

http://blogs.msdn.com/b/fsharpteam/archive/2012/07/19/more-about-fsharp-3.0-language-features.aspx

J'étais plutôt optimiste alors j'ai essayé

[<CLIMutable>]
type MyRecord = { Column1 : int; Column2 : int }

query {
    for d in context.table do
    groupBy {Column1 = col1; Column2 = col2} into g
    select (g.Key)
}

Malheureusement, j'obtiens exactement la même exception.

0 votes

Est-ce que le problème est que tu regroupes sur un tuple, ou bien que tu en sélectionnant un ? L'erreur semble concerner le type de "sortie". Peut-être que sélectionner g.Count() à la place et voir ce qui se passe. Je n'ai pas essayé la version 3.0... je ne fais que supposer.

0 votes

J'ai essayé de sélectionner une constante "select (1)" et j'ai obtenu la même chose.

0 votes

@ildjarn : g.Key est un tuple, qui n'a pas de constructeur sans paramètre. select 1 devrait cependant fonctionner, si ma supposition est correcte.

4voto

Paul Westcott Points 230

Voici un exemple de colonnes multiples utilisées pour le regroupement en c# et converties en f# (une gestion trop paranoïaque m'a fait tout renommer, mais je pense avoir été cohérent) :

(TheDatabase a été généré par SqlMetal, GetSummedValuesResult est un type d'enregistrement F#)

c#

public static class Reports
{
    public static IReadOnlyList<GetSummedValuesResult> GetSummedValues(TheDatabase db, DateTime startDate, DateTime? endDate)
    {
        var query =
            from sv in db.SomeValues

            where (sv.ADate >= startDate && sv.ADate <= (endDate ?? startDate))

            group sv by new { sv.ADate, sv.Owner.Name } into grouping

            select new GetSummedValuesResult(
                grouping.Key.ADate,
                grouping.Key.Name,
                grouping.Sum(g => g.Value)
            );

        return query.ToList();
    }
}

f#

type Reports() =
    static member GetSummedValues (db:TheDatabase) startDate (endDate:Nullable<DateTime>) =
        let endDate = if endDate.HasValue then endDate.Value else startDate

        let q = query {
            for sv in db.SomeValues do
            where (sv.ADate >= startDate && sv.ADate <= endDate)

            let key = AnonymousObject<_,_>(sv.ADate, sv.Owner.Name)
            groupValBy sv key into grouping

            select {
                ADate        = grouping.Key.Item1;
                AName        = grouping.Key.Item2;
                SummedValues = grouping.Sum (fun (g:TheDatabaseSchema.SomeValues) -> g.Value)
            }
        }

        List(q) :> IReadOnlyList<GetSummedValuesResult>

Donc la chose à utiliser est Microsoft.FSharp.Linq.RuntimeHelpers.AnonymousObject

Notez que vous ne devez pas utiliser le module Seq pour les fonctions d'agrégation !

SummedValues  = grouping |> Seq.sumBy (fun g -> g.SomeValues)

Bien que cette méthode puisse fonctionner, elle effectue l'agrégation du côté client, plutôt que de formuler le SQL approprié.

1voto

tomasK Points 140

Je vois ceci dans le premier de vos liens, je pense que c'est ce que vous voulez :

query {
    for student in db.Student do
    groupValBy student.Name student.Age into g
    select (g, g.Key, g.Count())
}

0 votes

GroupValBy est décrit comme suit : "Sélectionne une valeur pour chaque élément sélectionné jusqu'à présent et regroupe les éléments par la clé donnée". Dans ce cas, la clé est toujours basée sur student.Name.

1 votes

Alors, pourquoi y a-t-il ce student.Age ? Je n'arrive pas à croire qu'il n'existe pas de moyen simple de regrouper par plusieurs colonnes dans FSharp. Je vois maintenant un cas similaire dans la jointure - comment écrire une clé composite ?

2 votes

En fait, ce groupe ne sera que par student.Age . La première arg groupValBy est la valeur qui sera stockée, la seconde est la clé pour regrouper par. Imaginez le mot by entre le premier et le second argument.

0voto

ntziolis Points 7360

Tout d'abord, il faut se rappeler qu'une requête est traduite en termes réels. SQL à un moment donné. Il semble que linq ne prenne pas en charge l'utilisation de plusieurs clés de groupe en tant qu'élément d'information. Tuple<> . Par conséquent, toute transformation en Tuple<> doit être fait après que l'appel à la base de données soit terminé.

Deuxièmement, vous devriez être en mesure de réaliser regroupement de plusieurs clés en effectuant plusieurs regroupements les uns derrière les autres sur les touches respectives :

query {
    for d1 in context.table do
    groupBy d1.col1 into g1
    for d2 in g1 do
    groupBy d2.col2 into g2
    select g2
}

Je vous prie d'avoir pitié de moi si la syntaxe n'est pas à 100 % car F# n'est pas ma langue maternelle :) Le concept devrait cependant fonctionner parfaitement.

0 votes

Il est intéressant de noter qu'on obtient toujours "Only parameterless constructors and initializers are supported in LINQ to Entities".

0voto

Larry Hector Points 154

Lorsque vous utilisez groupBy, vous devez sélectionner une fonction d'agrégation (par exemple, count, sum, avg...).

0 votes

L'exemple est vraiment juste pour l'illustration, utiliser select (g.Key) n'est pas utile mais devrait fonctionner... "select (g.Count())" donne la même erreur.

0voto

newcommer Points 66
open Microsoft.FSharp.Linq.RuntimeHelpers
open System.Linq

query {
    for d in context.table do
    let t = MutableTuple<_,_>(Item1=d.col1,Item2=d.col2)
    groupValBy d t into g
    select (g.Key,g.Count())
    }

0 votes

Il semble que cela fonctionne. Cependant, j'ai du mal à obtenir quelque chose de plus que select (g.Key). select (g.Count()) donne une erreur et je ne suis pas sûr de savoir comment faire l'agrégation sur d'autres colonnes. Je joue quand même avec l'idée

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X