groupby在F#3.0查询中的多个列

时间:2011-12-27 23:49:10

标签: f# f#-3.0

尝试使用F#3.0时,在按多列分组时会遇到一些障碍。显而易见的是

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

但是我得到了“LINQ to Entities中只支持无参数构造函数和初始值设定项”。异常。

我似乎无法在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

我意识到我的问题类似于 “Entity Framework and Anonymous Types in F#”但似乎是powerpack / F#2.x专注,我希望F#3.0有一个优雅的答案......有什么想法吗?

更新

我在阅读Brian的帖子时遇到了CLIMutable属性:

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

我很乐观所以我试过

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

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

不幸的是,我得到了完全相同的例外。

8 个答案:

答案 0 :(得分:3)

以下是用于在c#中进行分组并转换为f#的多列的示例(过度偏执管理使我重命名所有内容,但我相信我一直都是这样):

(数据库由SqlMetal生成,GetSummedValuesResult是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>

所以要使用的是Microsoft.FSharp.Linq.RuntimeHelpers.AnonymousObject

请注意,您不应将Seq模块用于聚合功能!!

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

虽然这样做会有效,但它会在客户端进行聚合,而不是制定适当的SQL。

答案 1 :(得分:1)

我在你的第一个链接中看到这个,我认为这就是你想要的:

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

答案 2 :(得分:0)

首先,您必须记住,查询在某些时候会被转换为实际的SQL。似乎linq不支持将多个组密钥用作Tuple<>。因此,在数据库调用完成后,必须完成对Tuple<>的任何转换。

其次,您应该能够通过在相应的键上执行多个分组来实现多键分组

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

如果语法不是100%,请怜悯我,因为F#不是我的母语:)但这个概念应该可以正常工作。

答案 3 :(得分:0)

使用groupBy时,您需要选择一个聚合函数(例如count,sum,avg ...)。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

显然,可以使用Linq RuntimeHelpers中的groupValByAnonymousObject<_,_>的组合来表示一种解决方案。

例如。

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

答案 7 :(得分:-1)

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