用戶端與伺服器評估

根據一般規則,Entity Framework Core 會嘗試盡可能評估伺服器上的查詢。 EF Core 會將查詢的一部分轉換成參數,其可在用戶端進行評估。 查詢的其餘部分(以及產生的參數)會提供給資料庫提供者,以判斷要評估在伺服器上的對等資料庫查詢。 EF Core 支援最上層投影中的部分用戶端評估(基本上,最後一次呼叫 Select() )。 如果查詢中的最上層投影無法轉譯至伺服器,EF Core 會從伺服器擷取任何必要的資料,並在用戶端上評估查詢的其餘部分。 如果 EF Core 偵測到運算式,在無法轉譯為伺服器的最上層投影以外的任何位置,則會擲回執行時間例外狀況。 請參閱 查詢 的運作方式,以瞭解 EF Core 如何判斷無法轉譯為伺服器的內容。

注意

在 3.0 版之前,Entity Framework Core 支援查詢中的任何位置的用戶端評估。 如需詳細資訊,請參閱 舊版一節

提示

您可以檢視本文中的 GitHut 範例

最上層投影中的用戶端評估

在下列範例中,會使用協助程式方法來標準化部落格的 URL,而部落格是從 SQL Server 資料庫傳回的。 由於 SQL Server 提供者無法深入瞭解此方法的實作方式,因此無法將其轉譯為 SQL。 查詢的所有其他層面都會在資料庫中進行評估,但透過這個方法傳 URL 回的 是在用戶端上完成。

var blogs = context.Blogs
    .OrderByDescending(blog => blog.Rating)
    .Select(
        blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog.Url) })
    .ToList();
public static string StandardizeUrl(string url)
{
    url = url.ToLower();

    if (!url.StartsWith("http://"))
    {
        url = string.Concat("http://", url);
    }

    return url;
}

不支援的用戶端評估

雖然用戶端評估很有用,但有時可能會導致效能不佳。 請考慮下列查詢,其中 Helper 方法現在會用於其中篩選準則。 因為無法在資料庫中套用篩選準則,因此所有資料都必須提取到記憶體中,才能在用戶端上套用篩選。 根據伺服器上的篩選和資料量,用戶端評估可能會導致效能不佳。 因此 Entity Framework Core 會封鎖這類用戶端評估,並擲回執行時間例外狀況。

var blogs = context.Blogs
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

明確用戶端評估

在某些情況下,您可能需要明確強制進行用戶端評估,例如下列情況

  • 資料量很小,因此在用戶端上進行評估不會產生巨大的效能損失。
  • 使用的 LINQ 運算子沒有伺服器端轉譯。

在這種情況下,您可以藉由呼叫 或 ToListAsAsyncEnumerableToListAsync 或 非同步 AsEnumerable 等方法,明確地加入宣告用戶端評估。 藉由使用 AsEnumerable ,您會串流結果,但使用 ToList 會造成緩衝,方法是建立清單,這也會佔用額外的記憶體。 雖然如果您要列舉多次,但將結果儲存在清單中有助於更多,因為資料庫只有一個查詢。 根據特定使用方式,您應該評估哪一種方法對案例更有用。

var blogs = context.Blogs
    .AsEnumerable()
    .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
    .ToList();

提示

如果您使用 AsAsyncEnumerable 並想要在用戶端進一步撰寫查詢,則可以使用 System.Interactive.Async 程式庫來定義非同步可列舉的運算子。 如需詳細資訊,請參閱 用戶端 linq 運算子

用戶端評估中潛在的記憶體流失

因為查詢轉譯和編譯成本很高,因此 EF Core 會快取編譯的查詢計劃。 快取的委派可能會在執行最上層投影的用戶端評估時使用用戶端程式代碼。 EF Core 會為樹狀結構的用戶端評估部分產生參數,並藉由取代參數值來重複使用查詢計劃。 但是運算式樹狀結構中的某些常數無法轉換成參數。 如果快取的委派包含這類常數,則這些物件無法進行垃圾收集,因為它們仍在被參考中。 如果這類物件包含 DbCoNtext 或其他服務,則可能會導致應用程式記憶體使用量隨著時間成長。 此行為通常是記憶體流失的標誌。 EF Core 會在遇到無法使用目前資料庫提供者對應之類型的常數時擲回例外狀況。 常見的原因及其解決方案如下:

  • 使用實例方法:在用戶端投影中使用實例方法 時,運算式樹狀結構會包含 實例的常數。 如果您的方法未使用 實例中的任何資料,請考慮將 方法設定為靜態。 如果您需要方法主體中的實例資料,請將特定資料當做引數傳遞至 方法。
  • 將常數引數傳遞至 方法 :此案例通常會在 this 用戶端方法的引數中使用 。 請考慮將 中的引數分割成多個純量引數,而此引數可由資料庫提供者對應。
  • 其他常數:如果在任何其他情況下遇到常數 ,您可以評估是否需要常數進行處理。 如果需要有常數,或者您無法使用上述案例中的解決方案,請建立區域變數來儲存值,並在查詢中使用區域變數。 EF Core 會將區域變數轉換成 參數。

舊版

下一節適用于 3.0 之前的 EF Core 版本。

舊版 EF Core 支援查詢的任何部分的用戶端評估,而不只是最上層投影。 這就是為什麼類似在 [不支援的用戶端評估 ] 區段下 張貼的查詢正常運作的原因。 由於此行為可能會導致未察覺的效能問題,因此 EF Core 會記錄用戶端評估警告。 如需檢視記錄輸出的詳細資訊,請參閱 記錄

選擇性地,EF Core 可讓您將預設行為變更為擲回例外狀況,或在執行用戶端評估時不執行任何動作(除了在投影中除外)。 例外狀況擲回行為會使其類似于 3.0 中的行為。 若要變更行為,您必須在設定內容的選項時設定警告,通常是在 DbContext.OnConfiguringStartup.cs 中使用 ASP.NET Core。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;")
        .ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}