【问题标题】:Entity Framework: Efficiently grouping by month实体框架:按月有效分组
【发布时间】:2012-03-27 08:53:27
【问题描述】:

我对此进行了一些研究,到目前为止我发现的最好的方法是在整个数据集上使用 Asenumerable,以便在 linq 中对对象进行过滤,而不是在 DB 上。我正在使用最新的 EF。

我的工作(但很慢)代码是:

        var trendData = 
            from d in ExpenseItemsViewableDirect.AsEnumerable()
            group d by new {Period = d.Er_Approved_Date.Year.ToString() + "-" + d.Er_Approved_Date.Month.ToString("00") } into g
            select new
            {
                Period = g.Key.Period,
                Total = g.Sum(x => x.Item_Amount),
                AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2)
            };

这给了我格式 YYYY-MM 的月份,以及总金额和平均金额。但是每次都需要几分钟。

我的另一个解决方法是在 SQL 中执行更新查询,因此我有一个 YYYYMM 字段可以作为本机分组依据。更改数据库并非易事,但如有任何建议,我们将不胜感激。

我发现上述代码想法的线程 (http://stackoverflow.com/questions/1059737/group-by-weeks-in-linq-to-entities) 提到“等到 .NET 4.0”。有什么最近推出的东西可以帮助解决这种情况吗?

【问题讨论】:

    标签: c# .net entity-framework


    【解决方案1】:

    性能不佳的原因是整个表被提取到内存中(AsEnumerable())。然后你可以像这样按年和月分组

    var trendData = 
                (from d in ExpenseItemsViewableDirect
                group d by new {
                                Year = d.Er_Approved_Date.Year, 
                                Month = d.Er_Approved_Date.Month 
                                } into g
                select new
                {
                    Year = g.Key.Year,
                    Month = g.Key.Month,
                    Total = g.Sum(x => x.Item_Amount),
                    AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2)
                }
           ).AsEnumerable()
            .Select(g=>new {
                  Period = g.Year + "-" + g.Month,
                  Total = g.Total,
                   AveragePerTrans = g.AveragePerTrans
             });
    

    编辑

    根据我的回复,原始查询试图在 int 和 string 之间进行连接,EF 无法将其转换为 SQL 语句。我可以使用 SqlFunctions 类,但它的查询有点难看。所以我添加了AsEnumerable()分组后,意思是EF会在服务器上执行分组查询,会得到年月等,但是自定义对对象进行投影(AsEnumerable() 之后的内容)。

    【讨论】:

    • 非常好,非常感谢阿德里安。我刚刚做了一个快速测试,你的测试大约需要 3.5 秒,而原始测试需要 5.2 秒。长时间的延迟一定是我程序中的其他步骤。感谢您的努力,您的代码已投入使用!
    • 这不应该是公认的答案。这只是一种解决方法! @cryss 的回答给出了完美的结果。
    • 优秀的解决方案,“group by new”真的很强大,可以简化复杂的查询。
    【解决方案2】:

    当涉及到按月分组时,我更喜欢以这种方式完成此任务:

    var sqlMinDate = (DateTime) SqlDateTime.MinValue;
    
    var trendData = ExpenseItemsViewableDirect
        .GroupBy(x => SqlFunctions.DateAdd("month", SqlFunctions.DateDiff("month", sqlMinDate, x.Er_Approved_Date), sqlMinDate))
        .Select(x => new
        {
            Period = g.Key // DateTime type
        })
    

    因为它在分组结果中保留了日期时间类型。

    【讨论】:

    • 这绝对是公认的答案!简单的 MS-SQL 月份分隔转换为 linq-to-entity!
    • 为什么不GroupBy(g=>new{g.DateTime.Year, g.DateTime.Month}
    【解决方案3】:

    与crys 所写的类似,我正在为EF 执行以下操作。请注意,我们必须使用 EntityFunctions 才能调用 EF 支持的所有 DB 提供程序。 SqlFunctions 仅适用于 SQLServer。

    var sqlMinDate = (DateTime) SqlDateTime.MinValue; 
    
    (from x in ExpenseItemsViewableDirect
    let month = EntityFunctions.AddMonths(sqlMinDate, EntityFunctions.DiffMonths(sqlMinDate, x.Er_Approved_Date))
    group d by month 
    into g
    select new
    {
    Period = g.Key,
       Total = g.Sum(x => x.Item_Amount),
       AveragePerTrans = Math.Round(g.Average(x => x.Item_Amount),2)
    }).Dump();
    

    体验生成的 SQL(来自类似架构):

    -- Region Parameters
    DECLARE @p__linq__0 DateTime2 = '1753-01-01 00:00:00.0000000'
    DECLARE @p__linq__1 DateTime2 = '1753-01-01 00:00:00.0000000'
    -- EndRegion
    SELECT 
    1 AS [C1], 
    [GroupBy1].[K1] AS [C2], 
    [GroupBy1].[A1] AS [C3]
    FROM ( SELECT 
        [Project1].[C1] AS [K1], 
        FROM ( SELECT 
            DATEADD (month, DATEDIFF (month, @p__linq__1, [Extent1].[CreationDate]), @p__linq__0) AS [C1]
            FROM [YourTable] AS [Extent1]
        )  AS [Project1]
        GROUP BY [Project1].[C1]
    )  AS [GroupBy1]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-24
      • 2021-02-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多