将查询从SQL转换为EF Lambda表达式

时间:2017-07-26 16:04:22

标签: c# sql-server entity-framework linq

我在将SQL查询转换为相应的EF查询时遇到问题。我很接近,但我认为我错过了左连接的东西。

这是我的SQL(一个有点人为的例子):

SELECT
    Count(*), -- count posts
    Tag.Name,
    ISNULL(Category.Name, 'Other')
FROM Post
    INNER JOIN Tag ON Post.TagID=Tag.ID
    LEFT OUTER JOIN Category ON Tag.CategoryID=Category.ID
GROUP BY
    Tag.Name, ISNULL(Category.Name, 'Other')

帖子有0-1个标签(就像我说的,有点做作的例子)。标签有0-1类别。因此,INNERLEFT联接非常重要。

这是我不太正确的EF查询:

var counts = ctx.Posts
                .GroupBy(po =>
                            new
                            {
                                Tag = po.Tag.Name,
                                Category = po.Tag.Category.Name ?? "Other"
                            })
                .Select(agg =>
                            new
                            {
                                NumberOfPosts = agg.Count(),
                                Tag = agg.Key.Tag,
                                Category = agg.Key.Category
                            })
                .ToList();

此EF查询导致此SQL查询,这不是正确的:

SELECT 
    1 AS [C1], 
    [GroupBy1].[A1] AS [C2], 
    [GroupBy1].[K1] AS [Name], 
    [GroupBy1].[K2] AS [C3]
    FROM ( SELECT 
        [Join2].[K1] AS [K1], 
        [Join2].[K2] AS [K2], 
        COUNT([Join2].[A1]) AS [A1]
        FROM ( SELECT 
            [Extent2].[Name] AS [K1], 
            CASE WHEN ([Extent3].[Name] IS NULL) THEN N'Other' ELSE [Extent3].[Name] END AS [K2], 
            1 AS [A1]
            FROM   [dbo].[Post] AS [Extent1]
            LEFT OUTER JOIN [dbo].[Tag] AS [Extent2] ON [Extent1].[TagID] = [Extent2].[ID]
            LEFT OUTER JOIN [dbo].[Category] AS [Extent3] ON [Extent2].[CategoryID] = [Extent3].[ID]
        )  AS [Join2]
        GROUP BY [K1], [K2]
    )  AS [GroupBy1]

其中一个联接不正确。另外,我不确定GROUP BY是否正确处理了ISNULL(它非常重要,因为我希望它与空值一起分组以及DB中带有&#34的值;其他"一起评价为一个。)

我该如何解决这个问题?或者这只是我需要回归其他东西(一个杂项或视图)的有趣场景之一?

VS2017 / C#/。NET4.7 / EF6.13 / SQLAzure

(编辑添加生成的SQL语句)

1 个答案:

答案 0 :(得分:2)

参考导航属性生成的连接类型取决于导航属性的设置方式 - Required - > inner joinOptional - > left outer join

由于您的关系都是可选的,因此生成的SQL使用left outer join s。

只需插入.Where(po => po.Tag)即可生成正确的结果。我也希望EF能够聪明地将相应的left outer join转换为inner join,但事实并非如此。

但是,插入中间投影然后应用非空过滤器可以解决问题:

var counts = ctx.Posts
    .Select(po => new { po.Tag })
    .Where(po => po.Tag != null)
    .GroupBy(po => new
    {
        Tag = po.Tag.Name,
        Category = po.Tag.Category.Name ?? "Other"
    })
    .Select(agg => new
    {
        NumberOfPosts = agg.Count(),
        Tag = agg.Key.Tag,
        Category = agg.Key.Category
    })
    .ToList();

生成所需的连接类型:

SELECT
    1 AS [C1],
    [GroupBy1].[A1] AS [C2],
    [GroupBy1].[K1] AS [Name],
    [GroupBy1].[K2] AS [C3]
    FROM ( SELECT
        [Filter1].[K1] AS [K1],
        [Filter1].[K2] AS [K2],
        COUNT([Filter1].[A1]) AS [A1]
        FROM ( SELECT
            [Extent2].[Name] AS [K1],
            CASE WHEN ([Extent3].[Name] IS NULL) THEN N'Other' ELSE [Extent3].[Name] END AS [K2],
            1 AS [A1]
            FROM   [dbo].[Post] AS [Extent1]
            INNER JOIN [dbo].[Tag] AS [Extent2] ON [Extent1].[TagId] = [Extent2].[Id]
            LEFT OUTER JOIN [dbo].[Category] AS [Extent3] ON [Extent2].[CategoryId] = [Extent3].[Id]
            WHERE 1 = 1
        )  AS [Filter1]
        GROUP BY [K1], [K2]
    )  AS [GroupBy1]

唯一的冗余是WHERE 1=1,但SQL查询优化器应该能够消除它。