2016-08-19 3 views
1

Я хочу создать этот SQL-запрос:Linq левое внешнее соединение с графом

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()

так, что он может рассчитывать PlayerId из PlayerTableWinning вместо COUNT (1)


Обновлено: @Ivan Stoev

По добавив g в группу.

group new { p, s, g } 

И подвести группу

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

Он возвращает правильный ответ. Тем не менее, он использует SUM вместо count. 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 
+0

Разве это не на самом деле? Также в вашем sql - вы считаете 'playerId', но на самом деле все, что он подсчитывает количество записей в группе, - поэтому не имеет значения, является ли оно 1 или идентификатором игрока –

+0

, это не отличается от count (1) и count (PlayerId). что вы хотите на самом деле. –

+0

@EpoWilliam - Если вы не хотите знать разное количество playerId - это случай? –

ответ

1

только (но существенное) различие между SQL COUNT(field) и COUNT(1) является то, что прежний за исключением NULL значения, которые при нанесении на обычно требуется поле с правой стороны левое внешнее соединение, как в вашем случае, приводит к другому результату, когда нет совпадающих записей - первый возвращает 0, а последний возвращает 1.

«Естественный» эквивалент LINQ будет Count(field != null), но, к сожалению, он переведен на совсем другой SQL от текущего поставщика запросов EF. Поэтому в таких случаях я лично использую более близкое эквивалентное выражение 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) 
+0

Спасибо. Он возвращает правильный ответ. Тем не менее, он использует SUM вместо COUNT, так как вы можете увидеть SQL-запрос, сгенерированный, как показано в обновленном вопросе. – EpoWilliam

+0

Да, я знаю (и, думаю, я упоминал, почему в ответе). Это самое близкое, что вы можете получить от EF, нет конструкции LINQ, которая позволяет EF генерировать точную конструкцию SQL в вопросе. –

+0

Btw, 'item.w! = Null' обычно работает хорошо, когда вы присоединяетесь к одному полю. В вашем случае он производит избыточные проверки: '(int?) Item.w.PlayerId! = Null' может быть лучшим выбором. –