groupby multiple columns in a F# 3.0 query

我怕爱的太早我们不能终老 提交于 2019-12-18 11:00:34

问题


Just trying out F# 3.0 and hit a bit of a wall when it comes to grouping by multiple columns. The obvious thing to try was

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

But I get a "Only parameterless constructors and initializers are supported in LINQ to Entities." exception.

I can't seem to find an example on 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

And I realize my question is similar to " Entity Framework and Anonymous Types in F#" but it seems to be powerpack/F#2.x focused and I'm hoping F# 3.0 has an elegant answer... Any ideas?

UPDATE:

I came across the CLIMutable attribute from reading Brian's post at:

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

I was pretty optimistic so I tried

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

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

Unfortunately I get the exact same exception.


回答1:


The following is an example of multiple columns being used for grouping in c# and converted to f# (overly paranoid management has made me rename everything, but I believe I have been consistent):

(TheDatabase was generated by SqlMetal, GetSummedValuesResult is a F# record type)

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>

So the thing to use is Microsoft.FSharp.Linq.RuntimeHelpers.AnonymousObject

Note that you should not use the Seq module for aggregation functions!!

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

Although this WILL WORK, it does the aggregation on the client side, rather than formulating appropriate SQL.




回答2:


I see this in first of your links, I think it is what you want:

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



回答3:


//in F# 3.0
open Microsoft.FSharp.Linq.RuntimeHelpers
open Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter
open System.Linq

//[<CLIMutable>]
//type MyRecord = { Column1 : int; Column2 : int }
// require constructor in F#
// groupBy is not valid

type T(column1 : int, column2 : int)
    member val Colum1=colum1 with get,set
    membre val Colum2=colum2 with get,set

query {
    for d in context.table do
    groupValBy d (NewAnonymousObjectHelper(T(d.Colum1,d.Colume2))) into g
    select (g.Key)
    }



回答4:


When using groupBy you need to select an aggregating function (e.g. count, sum, avg...).




回答5:


First you have to remember that a query is translated into actual SQL at some point. It appears that linq does not support the use of multiple group keys as a Tuple<>. Therefore any transformation into Tuple<> has to be done after the database call has completed.

Second, you should be able to achieve multiple key grouping by performing multiple groupings behind each other on the respective keys:

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

Please have mercy with me if the syntax is not 100% since F# is not my native tongue :) The concept however should work just fine.




回答6:


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())
    } 



回答7:


Apparently there may be a solution as indicated in by using a combination of groupValBy and AnonymousObject<_,_> from the Linq RuntimeHelpers.

ex.

query {
    for d in context.table do
    let key = AnonymousObject<_,_>(d.col1,d.col2)
    groupValBy d key into g
    select (g.Key)
    } 



回答8:


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


来源:https://stackoverflow.com/questions/8650463/groupby-multiple-columns-in-a-f-3-0-query

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!