【问题标题】:Linq Left Outer Join with Count带有计数的 Linq 左外连接
【发布时间】:2016-08-19 06:36:21
【问题描述】:

我想创建这个 SQL 查询:

SELECT 
    a.[Seat], 
    b.[PlayerId], 
    b.[UserName], 
    b.[NickName],
    COUNT(c.PlayerId) AS Trophy
    FROM   [dbo].[tbl_PlayerTableSeat] AS a
    INNER JOIN [dbo].[tbl_Player] AS b ON a.[PlayerId] = b.[PlayerId]               
    INNER JOIN [dbo].[tbl_GameVirtualTable] AS d ON d.GameVirtualTableId = a.GameVirtualTableId
    LEFT OUTER JOIN [dbo].[tbl_PlayerTableWinning] AS c ON a.[PlayerId] = c.[PlayerId] AND c.GameTableId = d.GameTableId                
    WHERE a.GameVirtualTableId = 36
    GROUP BY a.[Seat], b.[PlayerId], b.[UserName], b.[NickName]

我有这个 Linq

var virtualTableSeatList = (from s in db.PlayerTableSeat
                        join p in db.Player on s.PlayerId equals p.PlayerId
                        join v in db.GameVirtualTable on s.GameVirtualTableId equals v.GameVirtualTableId
                        join w in db.PlayerTableWinning on new { X1 = s.PlayerId, X2 = v.GameTableId } equals new { X1 = w.PlayerId, X2 = w.GameTableId } into gj

                        from g in gj.DefaultIfEmpty()
                        where s.GameVirtualTableId == virtualGameTableId
                        group new { p, s } by new { p.PlayerId, s.Seat, p.NickName, p.UserName } into grp

                        select new VirtualTableSeatDto
                        {
                            PlayerId = grp.Key.PlayerId,
                            Seat = grp.Key.Seat,
                            NickName = grp.Key.NickName,
                            UserName = grp.Key.UserName,                                            
                            Trophy = grp.Count()
                        }
               ).ToList();

从 SQL Profiler,Linq 生成以下 SQL 查询:

exec sp_executesql N'SELECT 
[GroupBy1].[K2] AS [PlayerId], 
 CAST( [GroupBy1].[K1] AS int) AS [C1], 
[GroupBy1].[K4] AS [NickName], 
[GroupBy1].[K3] AS [UserName], 
[GroupBy1].[A1] AS [C2]
FROM ( SELECT 
    [Extent1].[Seat] AS [K1], 
    [Extent2].[PlayerId] AS [K2], 
    [Extent2].[UserName] AS [K3], 
    [Extent2].[NickName] AS [K4], 
    COUNT(1) AS [A1]
    FROM    [dbo].[tbl_PlayerTableSeat] AS [Extent1]
    INNER JOIN [dbo].[tbl_Player] AS [Extent2] ON [Extent1].[PlayerId] = [Extent2].[PlayerId]
    INNER JOIN [dbo].[tbl_GameVirtualTable] AS [Extent3] ON [Extent1].[GameVirtualTableId] = [Extent3].[GameVirtualTableId]
    LEFT OUTER JOIN [dbo].[tbl_PlayerTableWinning] AS [Extent4] ON ([Extent1].[PlayerId] = [Extent4].[PlayerId]) AND ([Extent3].[GameTableId] = [Extent4].[GameTableId])
    WHERE [Extent1].[GameVirtualTableId] = @p__linq__0
    GROUP BY [Extent1].[Seat], [Extent2].[PlayerId], [Extent2].[UserName], [Extent2].[NickName]
)  AS [GroupBy1]',N'@p__linq__0 int',@p__linq__0=36

我想把COUNT(1) AS [A1]改成COUNT([Extent4].[PlayerId]) AS [A1]

所以它可以返回正确的数据。 我不知道如何更改 LinQ

Trophy = grp.Count()

这样它就可以计算 PlayerIdPlayerTableWinning 而不是 COUNT(1)


更新:@Ivan Stoev

通过将 g 添加到组中。

group new { p, s, g }

对组求和

Trophy = grp.Sum(item => item.w != null ? 1 : 0)

它返回正确的答案。但是,它使用 SUM 而不是计数。生成的SQL查询如下:

exec sp_executesql N'SELECT 
[GroupBy1].[K2] AS [PlayerId], 
 CAST( [GroupBy1].[K1] AS int) AS [C1], 
[GroupBy1].[K4] AS [NickName], 
[GroupBy1].[K3] AS [UserName], 
[GroupBy1].[A1] AS [C2]
FROM ( SELECT 
    [Filter1].[K1] AS [K1], 
    [Filter1].[K2] AS [K2], 
    [Filter1].[K3] AS [K3], 
    [Filter1].[K4] AS [K4], 
    SUM([Filter1].[A1]) AS [A1]
    FROM ( SELECT 
        [Extent1].[Seat] AS [K1], 
        [Extent2].[PlayerId] AS [K2], 
        [Extent2].[UserName] AS [K3], 
        [Extent2].[NickName] AS [K4], 
        CASE WHEN ( NOT (([Extent4].[GameTableId] IS NULL) AND ([Extent4].[PlayerId] IS NULL) AND ([Extent4].[GameRoundId] IS NULL))) THEN 1 ELSE 0 END AS [A1]
        FROM    [dbo].[tbl_PlayerTableSeat] AS [Extent1]
        INNER JOIN [dbo].[tbl_Player] AS [Extent2] ON [Extent1].[PlayerId] = [Extent2].[PlayerId]
        INNER JOIN [dbo].[tbl_GameVirtualTable] AS [Extent3] ON [Extent1].[GameVirtualTableId] = [Extent3].[GameVirtualTableId]
        LEFT OUTER JOIN [dbo].[tbl_PlayerTableWinning] AS [Extent4] ON ([Extent1].[PlayerId] = [Extent4].[PlayerId]) AND ([Extent3].[GameTableId] = [Extent4].[GameTableId])
        WHERE [Extent1].[GameVirtualTableId] = @p__linq__0
    )  AS [Filter1]
    GROUP BY [K1], [K2], [K3], [K4]
)  AS [GroupBy1]',N'@p__linq__0 int',@p__linq__0=36

【问题讨论】:

  • 其实不一样吗?同样在你的 sql 中 - 你计算 playerId 但实际上它只是计算组中的记录数 - 所以它是 1 还是玩家 id 都没有关系
  • count(1) 和 count(PlayerId) 没有区别。你到底想要什么。
  • @EpoWilliam - 除非你想知道 playerId 的不同数量 - 是这样吗?
  • SELECT count(1) 不管有没有记录,总是返回1。而 count(PlayerId) 只会返回实际的记录数。
  • 是的。即使没有记录,count(1) 也总是返回 1。但是count(PlayerId)如果没有记录则返回0。

标签: c# sql-server linq


【解决方案1】:

SQL COUNT(field)COUNT(1) 之间唯一(但重要的)区别是前者不包括 NULL 值,当从左外连接右侧应用于通常需要的字段时,如当没有匹配的记录时,您的案例会产生不同的结果 - 前者返回 0,而后者返回 1。

“自然”的 LINQ 等效项是 Count(field != null),但不幸的是,当前 EF 查询提供程序将其转换为完全不同的 SQL。所以在这种情况下,我个人会使用更接近的等效表达式Sum(field != null ? 1 : 0),它会产生更好的 SQL。

为了将上述内容应用于您的查询,您需要在分组中访问w,因此请更改

group new { p, s }

group new { p, s, w }

然后使用

Trophy = grp.Sum(item => item.w != null ? 1 : 0)

【讨论】:

  • 谢谢。它返回正确的答案。但是,它使用 SUM 而不是 COUNT,您可以看到生成的 SQL 查询如更新后的问题所示。
  • 是的,我知道(我想我在答案中提到了原因)。这是您可以从 EF 获得的最接近的结果,没有 LINQ 构造可以让 EF 在问题中生成确切的 SQL 构造。
  • 顺便说一句,item.w != null 通常在您加入一个字段时效果很好。在您的情况下,它会产生冗余检查,(int?)item.w.PlayerId != null 可能是更好的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-10
  • 1970-01-01
  • 1970-01-01
  • 2014-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多