2017-07-26 193 views
2

我無法將SQL查詢轉換爲適當的EF查詢。我很接近,但我想我錯過了左連接的東西。將查詢從SQL轉換爲EF Lambda表達式

這是我的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] 

其中一個連接是不正確。另外,我不確定ISNULL是否由GROUP BY正確處理(這很重要,因爲我希望它將空值和DB中的值與「其他」值一起組合爲一個值) 。

我該如何解決這個問題?或者,這只是其中一種有趣的場景,我需要回退到其他的東西(一個短片或視圖)?

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

(編輯補充所得SQL語句)

+0

你的LINQ查詢有什麼問題?對我來說看起來不錯 - 只要你有正確的實體映射,EF應該爲你生成正確的連接(通過導航屬性)。你可以發佈生成的SQL(用'ToString()')替換'ToList()'? –

+0

@IvanStoev有些東西與我的數量有關。我相信這兩個連接都是以'LEFT OUTER'連接的形式進入的,或者兩者都以'INNER'連接的形式進入。我正在處理隨機生成的數據,因此我不太清楚哪些數據,因爲我打了Azure,所以無法使用Profiler來獲取查詢。即使基數相同,我也特別想要兩種不同類型的連接。 – Jaxidian

+0

我明白了。根據你的解釋,最有可能兩個都是'left outer join'('Optional'relatonship)。如何獲得SQL查詢,你不需要一個分析器 - 只是一個調試器(請參閱我以前的評論:)無論如何,沒有辦法強制導航屬性生成不同的連接比指定的關係,所以我想你必須使用一個手動的'join' LINQ查詢來代替'po.Tag'。或者在'GroupBy'之前添加'.Where(po => po.Tag!= null)'。 –

回答

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查詢優化器應該能夠消除它。

+1

我認爲這樣做。感謝您發現額外的投影導致它切換連接類型。我想我需要玩這個有點爲我的最後非簡化的查詢(主要是用這個投影),但我認爲我現在可以自己管理,我知道這個方法可以做到這一點。謝謝!! – Jaxidian

+0

不用客氣,我相信你會的! –