EF Core嵌套Linq在N + 1個SQL查詢中選擇結果

c# entity-framework-core linq select-n-plus-1 sql-server

我有一個數據模型,其中'Top'對像有0到N'Sub'對象。在SQL中,這是通過外鍵dbo.Sub.TopId

var query = context.Top
    //.Include(t => t.Sub) Doesn't seem to do anything
    .Select(t => new {
        prop1 = t.C1,
        prop2 = t.Sub.Select(s => new {
            prop21 = s.C3 //C3 is a column in the table 'Sub'
        })
        //.ToArray() results in N + 1 queries
    });
var res = query.ToArray();

在Entity Framework 6(延遲加載關閉)中,此Linq查詢將轉換為單個 SQL查詢。結果將被完全加載,因此res[0].prop2將是已經填充的IEnumerable<SomeAnonymousType>

使用EntityFrameworkCore(NuGet v1.1.0)時,子集合尚未加載並且類型為:

System.Linq.Enumerable.WhereSelectEnumerableIterator<Microsoft.EntityFrameworkCore.Storage.ValueBuffer, <>f__AnonymousType1<string>>.

在迭代數據之前,不會加載數據,從而導致N + 1個查詢。當我向查詢添加.ToArray() (如註釋中所示),使用SQL分析器將數據完全加載到var res ,但是顯示這不再在1個SQL查詢中實現。對於每個“Top”對象,執行“Sub”表上的查詢。

首先指定.Include(t => t.Sub)似乎沒有任何改變。匿名類型的使用似乎也不是問題,用new MyPocoClass { ... }替換new { ... }new MyPocoClass { ... }不會改變任何東西。

我的問題是: 有沒有辦法讓行為類似於EF6,所有數據都立即加載?


注意 :我意識到在這個例子中,問題可以通過執行查詢在內存中創建匿名對象來解決,如下所示:

var query2 = context.Top
    .Include(t => t.Sub)
    .ToArray()
    .Select(t => new //... select what is needed, fill anonymous types

然而,這只是一個例子,我確實需要創建對像作為Linq查詢的一部分,因為AutoMapper使用它來填充我的項目中的DTO


更新:使用新的EF Core 2.0測試,問題仍然存在。 (21-08-2017)

aspnet/EntityFrameworkCore GitHub repo: 問題4007上跟踪問題

更新:一年後,此問題已在版本2.1.0-preview1-final得到修復。 (2018年3月1日)

更新: EF版本2.1已發布,它包含一個修復程序。看下面的答案。 (2018年5月31日)

一般承認的答案

對於里程碑2.1.0-preview1 ,GitHub問題#4007已標記為closed-fixed 。現在2.1預覽1已經在NuGet上提供,正如這篇.NET博客文章中所討論的那樣。

版本2.1也已發布,使用以下命令安裝它:

Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.1.0

然後在嵌套的.Select(x => ...) .ToList()上使用.ToList()來指示應立即獲取結果。對於我原來的問題,這看起來像這樣:

var query = context.Top
    .Select(t => new {
        prop1 = t.C1,
        prop2 = t.Sub.Select(s => new {
            prop21 = s.C3
        })
        .ToList() // <-- Add this
    });
var res = query.ToArray(); // Execute the Linq query

這導致在數據庫上運行2個SQL查詢(而不是N + 1);首先是一個普通的SELECT FROM 'Top'表,然後是一個SELECT FROM '表,其中INNER JOIN FROM 'Top'表,基於Key-ForeignKey relation [Sub].[TopId] = [Top].[Id] 。然後將這些查詢的結果合併到內存中。

結果正如您所期望的那樣,與EF6將返回的內容非常相似:匿名類型'a的數組,其屬性為prop1prop2 ,其中prop2是匿名類型的列表'b ,其具有屬性prop21 。最重要的是, 所有這些都是在.ToArray()調用之後完全加載的!


熱門答案

我遇到了同樣的問題。

您提出的解決方案不適用於相對較大的表。如果您查看生成的查詢,那麼它將是一個沒有where條件的內連接。

var query2 = context.Top .Include(t => t.Sub).ToArray().Select(t => new // ...選擇需要的內容,填寫匿名類型

我通過重新設計數據庫解決了這個問題,但我很樂意聽到更好的解決方案。

在我的例子中,我有兩個表A和B.表A與B一對多。當我試圖用你所描述的列表直接解決它時我沒有設法做到(.NET的運行時間) LINQ為0.5秒,而.NET Core LINQ在運行30秒後失敗)。

因此,我必須為表B創建一個外鍵,並從表B的一側開始,沒有內部列表。

context.A.Where(a => a.B.ID == 1).ToArray();

之後,您可以簡單地操作生成的.NET對象。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因