Vyhodnocení klienta vs. serveru

Obecně platí, že Entity Framework Core se pokusí co nejvíce vyhodnotit dotaz na serveru. EF Core převádí části dotazu na parametry, které může vyhodnotit na straně klienta. Zbytek dotazu (spolu s vygenerovanými parametry) se poskytne poskytovateli databáze, aby určil ekvivalentní databázový dotaz, který se má vyhodnotit na serveru. EF Core podporuje částečné hodnocení klientů v projekci nejvyšší úrovně (v podstatě poslední volání Select()). Pokud není možné přeložit projekce nejvyšší úrovně v dotazu na server, EF Core načte všechna požadovaná data ze serveru a vyhodnotí zbývající části dotazu na klientovi. Pokud EF Core zjistí výraz, na jakémkoli jiném místě než projekce nejvyšší úrovně, která se nedá přeložit na server, vyvolá výjimku za běhu. Podívejte se, jak fungují dotazy, abyste pochopili, jak EF Core určuje, co nejde přeložit na server.

Poznámka

Před verzí 3.0 podporuje Entity Framework Core vyhodnocení klienta kdekoli v dotazu. Další informace najdete v předchozí části.

Tip

Ukázku pro tento článek najdete na GitHubu.

Hodnocení klientů v projekci nejvyšší úrovně

V následujícím příkladu se pomocná metoda používá ke standardizaci adres URL pro blogy, které se vrací z databáze SQL Serveru. Vzhledem k tomu, že poskytovatel SQL Serveru nemá žádný přehled o tom, jak se tato metoda implementuje, není možné ji přeložit do SQL. Všechny ostatní aspekty dotazu se vyhodnocují v databázi, ale předání vrácené URL prostřednictvím této metody se provádí v klientovi.

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;
}

Nepodporované vyhodnocení klienta

I když je hodnocení klientů užitečné, může to někdy vést k nízkému výkonu. Představte si následující dotaz, ve kterém se teď pomocná metoda používá ve filtru where. Protože filtr nelze použít v databázi, musí být všechna data načítaná do paměti, aby bylo možné použít filtr v klientovi. Na základě filtru a množství dat na serveru může vyhodnocení klienta vést k nízkému výkonu. Entity Framework Core proto blokuje takové vyhodnocení klienta a vyvolá výjimku za běhu.

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

Explicitní vyhodnocení klienta

V některých případech může být nutné vynutit explicitní vyhodnocení klienta, například v následujících případech.

  • Objem dat je malý, takže při vyhodnocování na klientovi se neúčtují velké pokuty za výkon.
  • Použitý operátor LINQ nemá žádný překlad na straně serveru.

V takových případech se můžete explicitně přihlásit k vyhodnocení klienta voláním metod, jako AsEnumerable jsou nebo ToList (AsAsyncEnumerable nebo ToListAsync pro asynchronní). Při použití AsEnumerable by se výsledky streamovaly, ale použití ToList by způsobilo ukládání do vyrovnávací paměti vytvořením seznamu, který také zabírá další paměť. I když když výčet několikrát zapíšete, ukládání výsledků do seznamu vám pomůže víc, protože databáze obsahuje jenom jeden dotaz. V závislosti na konkrétním využití byste měli vyhodnotit, která metoda je pro případ užitečnější.

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

Tip

Pokud používáte AsAsyncEnumerable a chcete vytvořit dotaz dále na straně klienta, můžete použít knihovnu System.Interactive.Async , která definuje operátory pro asynchronní enumerables. Další informace najdete v tématu operátory linq na straně klienta.

Potenciální nevracení paměti při vyhodnocení klienta

Vzhledem k tomu, že překlad a kompilace dotazů jsou nákladné, EF Core ukládá zkompilovaný plán dotazů do mezipaměti. Delegát uložený v mezipaměti může při vyhodnocování projekce nejvyšší úrovně používat klientský kód. EF Core generuje parametry pro části stromu vyhodnocené klientem a znovu použije plán dotazu nahrazením hodnot parametrů. Některé konstanty ve stromu výrazů se ale nedají převést na parametry. Pokud delegát uložený v mezipaměti obsahuje takové konstanty, pak tyto objekty nemohou být uvolněny z paměti, protože se na tyto konstanty stále odkazují. Pokud takový objekt obsahuje DbContext nebo jiné služby v něm, může způsobit růst využití paměti aplikace v průběhu času. Toto chování je obecně znaménkem nevracení paměti. EF Core vyvolá výjimku vždy, když dojde k konstantám typu, které nelze mapovat pomocí aktuálního poskytovatele databáze. Běžné příčiny a jejich řešení jsou následující:

  • Použití metody instance: Při použití metod instance v projekci klienta, strom výrazu obsahuje konstantu instance. Pokud vaše metoda nepoužívá žádná data z instance, zvažte, že je metoda statická. Pokud potřebujete data instance v těle metody, předejte konkrétní data jako argument metodě.
  • Předávání konstantních argumentů metodě: Tento případ obvykle vzniká použitím this argumentu pro metodu klienta. Zvažte rozdělení argumentu na více skalárních argumentů, které může namapovat zprostředkovatel databáze.
  • Další konstanty: Pokud se konstanta setká v jiném případě, můžete vyhodnotit, jestli je konstanta potřebná při zpracování. Pokud je potřeba mít konstantu nebo pokud nemůžete použít řešení z výše uvedených případů, vytvořte místní proměnnou pro uložení hodnoty a použití místní proměnné v dotazu. EF Core převede místní proměnnou na parametr.

Předchozí verze

Následující část platí pro verze EF Core starší než 3.0.

Starší verze EF Core podporovaly hodnocení klientů v jakékoli části dotazu – nejen v projekci nejvyšší úrovně. Proto správně fungovaly dotazy podobné dotazům publikovaným v části Nepodporované hodnocení klientů. Vzhledem k tomu, že toto chování může způsobit nechtěné problémy s výkonem, EF Core zaznamenalo upozornění na vyhodnocení klienta. Další informace o zobrazení výstupu protokolování najdete v tématu Protokolování.

Volitelně vám EF Core umožňuje změnit výchozí chování tak, aby při vyhodnocování klienta (s výjimkou projekce) vyvolal výjimku nebo nedělat nic. Výjimka, která vyvolává chování, by se podobala chování ve verzi 3.0. Pokud chcete změnit chování, musíte nakonfigurovat upozornění při nastavování možností pro váš kontext – obvykle v DbContext.OnConfiguringnebo v Startup.cs případě, že používáte 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));
}