无法 select 分组 Entity Framework

not able to select in Group by Entity Framework

我有这个 SQL 查询,我正试图将其翻译成 Linq

SELECT 
    DATEPART(yyyy, ce.DueDate) AS year,
    DATEPART(mm, ce.DueDate) AS Month,
    COUNT(CASE WHEN rt.Code = 'Pass' THEN 1 ELSE NULL END) AS NumPass,
    COUNT(CASE WHEN rt.Code = 'Fail' THEN 1 ELSE NULL END) AS NumFail
FROM
    ControlEvent ce
INNER JOIN 
    ProcessEvent pe ON pe.ControlEventId = ce.Id
INNER JOIN 
    ResultType rt ON pe.ResultTypeId = rt.Id
WHERE 
    DATEDIFF(dd,ce.DueDate,GETDATE()) <= 0
    AND DATEDIFF(dd,ce.DueDate,DATEADD(mm, 3, GETDATE())) >= 0
    AND pe.ProcessId = 1040
GROUP BY 
    DATEPART(yyyy, ce.DueDate), DATEPART(mm, ce.DueDate)
ORDER BY 
    DATEPART(yyyy, ce.DueDate), DATEPART(mm, ce.DueDate)

到目前为止我已经这样做了

var result =
   (from ce in ControlEvents

    join pe in ProcessEvents on ce.Id equals pe.ControlEventId
    join rt in ResultTypes on pe.ResultTypeId equals rt.Id into resultType

    where ce.DueDate >= startDate &&
    ce.DueDate <= endDate &&
    pe.ProcessId == 1048

    orderby ce.DueDate.Value.Year, ce.DueDate.Value.Month

    group ce by new {
       ce.DueDate.Value.Year,
       ce.DueDate.Value.Month,
    } into g

    select new {
       g.Key.Year,
       g.Key.Month,
    }

    ).ToList();

我的问题是如何将我的 SQL 查询中的 case 语句带到 linq Select。谢谢

首先,删除 into resultType,因为它会创建一个组连接,而您的 SQL 查询不使用此类结构。

其次,将orderby子句移到groupby之后。

最后,利用 SQL Count(CASE WHEN condition THEN 1 ELSE NULL END) 等同于 LINQ 支持的 SUM(condition, 1, 0) 的事实。

所以等效的 LINQ 查询可能是这样的:

var result =
   (from ce in ControlEvents
    join pe in ProcessEvents on ce.Id equals pe.ControlEventId
    join rt in ResultTypes on pe.ResultTypeId equals rt.Id
    where ce.DueDate >= startDate &&
        ce.DueDate <= endDate &&
        pe.ProcessId == 1048
    group rt by new {
       ce.DueDate.Value.Year,
       ce.DueDate.Value.Month,
    } into g
    orderby g.Key.Year, g.Key.Month
    select new {
       g.Key.Year,
       g.Key.Month,
       NumPass = g.Sum(e => e.Code == "Pass" ? 1 : 0),
       NumFail = g.Sum(e => e.Code == "Fail" ? 1 : 0)
    }
   ).ToList();

生成的 EF6.1.3 生成的 SQL 查询如下所示:

SELECT 
    [Project1].[C5] AS [C1], 
    [Project1].[C3] AS [C2], 
    [Project1].[C4] AS [C3], 
    [Project1].[C1] AS [C4], 
    [Project1].[C2] AS [C5]
    FROM ( SELECT 
        [GroupBy1].[A1] AS [C1], 
        [GroupBy1].[A2] AS [C2], 
        [GroupBy1].[K1] AS [C3], 
        [GroupBy1].[K2] AS [C4], 
        1 AS [C5]
        FROM ( SELECT 
            [Filter1].[K1] AS [K1], 
            [Filter1].[K2] AS [K2], 
            SUM([Filter1].[A1]) AS [A1], 
            SUM([Filter1].[A2]) AS [A2]
            FROM ( SELECT 
                DATEPART (year, [Extent1].[DueDate]) AS [K1], 
                DATEPART (month, [Extent1].[DueDate]) AS [K2], 
                CASE WHEN (N'Pass' = [Extent3].[Code]) THEN 1 ELSE 0 END AS [A1], 
                CASE WHEN (N'Fail' = [Extent3].[Code]) THEN 1 ELSE 0 END AS [A2]
                FROM   [dbo].[ControlEvents] AS [Extent1]
                INNER JOIN [dbo].[ProcessEvents] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ControlEventId]
                INNER JOIN [dbo].[ResultTypes] AS [Extent3] ON [Extent2].[ResultTypeId] = [Extent3].[Id]
                WHERE ([Extent1].[DueDate] >= @p__linq__0) AND ([Extent1].[DueDate] <= @p__linq__1) AND ([Extent2].[ProcessId] = @p__linq__2)
            )  AS [Filter1]
            GROUP BY [K1], [K2]
        )  AS [GroupBy1]
    )  AS [Project1]
    ORDER BY [Project1].[C3] ASC, [Project1].[C4] ASC

你快到了。就在您的 select 语句之前,您有一系列组,其中每个组都是一系列连接结果,其中每个连接结果具有相同的年份/月份。

例如,您有以下组

  • group1(2015 年 1 月)= 截止日期为 2015 年 1 月的连接结果序列
  • group2(2015 年 2 月)= 截止日期为 2015 年 2 月的连接结果序列
  • group3(2015 年 2 月)= 截止日期为 2015 年 3 月的连接结果序列

您已经发现密钥中包含您想要的年份和月份。

对于 2015 年 1 月组的 NumPass,您需要 2015 年 1 月序列中匹配 joinResult.resultType.code == "Pass"、

的所有元素

作为一个面向对象的程序员,我在写我的 Linq 语句时总是有点困难 SQL 这样的语法,所以如果你不介意的话,我用 lambda 表达式重写了它:

ControlEvents.Join(ProcessEvents,
    key1 => key1.Id,              // from ControlEvents take Id
    key2 => key2.ControlEventId   // from processEventt take ControlEventId
    (x, y) => new                 // where they match,
    {
        DueDate = x.DueDate,           // take ControlEvent.Duedate
        ProcessId = y.ProcessId,       // take ProcessId.Id
        ResultTypeId = y.ResultTypeId, // take Process.ResultTypeId
    })

.Where (joinResult =>                  // limit the join result before the 2nd join
    joinResult.DueDate >= startDate &&
    joinResult.DueDate <= endDate &&
    joinResult.ProcessId == 1048)

.Join(ResultTypes,             // join the previous result with ResultTypes
    key1 => key1.ResultTypeId  // from previous join take ResultTypeId
    key2 => key2.Id            // from ResultTypes takd Id
    (x, y) => new              // where they match, take:
    {
        Year = x.DueDate.year,
        Month = x.DueDate.Month,
        // ProcessId = x.ProcessId, not needed anymore
        // unless you want the where statement after the 2nd join 
        ResultCode = y.Code,
    })
 .Orderby(joinResult => joinResult.Year)
 .ThenBy(joinResult => joinResult.Month)
 .GroupBy(sortResult => new {Year = sortResult.Year, Month = sortResult.Month}
  • 现在你有关键字为 {Year, Month} 的组。
  • 每个组都有一系列具有属性 { Year, Month, ResultCode} 的对象
  • 一组内的年/月都一样

现在您所要做的就是计算一组中匹配 "Pass" 和匹配 "Fail" 的所有元素:

继续 LINQ 语句:

.Select(group => new
{
    Year = group.key.Year,
    Month = group.key.Month,
    NumPass = group
        .Where(groupElement => groupElement.ResultCode.Equals("Pass"))
        .Count(),
    NumFail = group
        .Where(groupElement => groupElement.ResultCode.Equals("Fail"))
        .Count(),
 }
 .ToList();

这应该可以解决问题。

请注意,我将 ProcessId == 1048 的 Where 语句放在第二次加入之前,因为我猜这会限制要加入的项目数量。也许以下甚至会更聪明:

ControlEvents
    .Where(controlEvent => controlEvent.DueDate >= startDate
           && controlEvent.DueDate <= endDate)
    .Join (ProcessEvents.Where(processEvent => processEvent.Id == 1048),
           key1 => etc,

我想这真的会限制加入的元素数量。

此外,请考虑在最终 select 之后按年/月排序,因为那样的话您还必须对更小的系列进行排序