无法在“按实体框架分组”中进行选择

时间:2016-02-29 14:15:21

标签: sql-server entity-framework linq linq-to-sql linq-to-entities

我有这个SQL查询,我试图将其转换为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 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();

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

{{1}}

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

2 个答案:

答案 0 :(得分:2)

首先,删除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

答案 1 :(得分:1)

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

例如,您有以下组

  • group1(2015年1月)= duedate jan 2015的加入结果序列
  • group2(2015年2月)= duedate feb 2015的加入结果序列
  • group3(2015年2月)= duedate mar 2015的加入结果序列

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

对于2015年1月组的NumPass,您希望2015年1月序列中的所有元素与joinResult.resultType.code ==“Pass”匹配,

作为一个面向对象的程序员,我总是在使用这一半SQL语法编写Linq语句时遇到一些困难,所以如果它不会打扰你太多,我会用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}
  • 属性的对象
  • 在一个组中,年/月都是相同的

现在你要做的就是计算一组中与“通过”匹配的所有元素以及与“失败”匹配的元素:

继续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();

这应该可以解决问题。

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

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

我想这确实会限制要加入的元素数量。

此外,考虑在最终选择之后按年/月排序,因为在这种情况下,您还必须订购更小的集合