Cliente versus Avaliação do Servidor

Como regra geral, o Entity Framework Core tenta avaliar uma consulta no servidor o máximo possível. O EF Core converte partes da consulta em parâmetros, que podem ser avaliados no lado do cliente. O restante da consulta (juntamente com os parâmetros gerados) é dado ao provedor de banco de dados para determinar a consulta de banco de dados equivalente a ser avaliada no servidor. O EF Core dá suporte à avaliação parcial do cliente na projeção de nível superior (essencialmente, a última chamada para Select()). Se a projeção de nível superior na consulta não puder ser traduzida para o servidor, o EF Core buscará todos os dados necessários do servidor e avaliará as partes restantes da consulta no cliente. Se o EF Core detectar uma expressão, em qualquer lugar que não seja a projeção de nível superior, que não pode ser traduzida para o servidor, ela gerará uma exceção de runtime. Veja Como as consultas funcionam para entender como o EF Core determina o que não pode ser traduzido para o servidor.

Observação

Antes da versão 3.0, o Entity Framework Core tinha suporte para avaliação de cliente em qualquer lugar da consulta. Para obter mais informações, confira a seção de versões anteriores.

Dica

Veja o exemplo deste artigo no GitHub.

Avaliação do cliente na projeção de nível superior

No exemplo a seguir, um método auxiliar é usado para padronizar URLs para blogs, que são retornadas de um banco de dados SQL Server. Como o provedor do SQL Server não tem nenhuma informação sobre como esse método é implementado, não é possível traduzi-lo para SQL. Todos os outros aspectos da consulta são avaliados no banco de dados, mas a transmissão do URL retornado por esse método é feita no cliente.

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

Avaliação de cliente sem suporte

Embora a avaliação do cliente seja útil, isso pode resultar em um desempenho ruim às vezes. Considere a consulta a seguir, na qual o método auxiliar agora é usado em um filtro where. Como o filtro não pode ser aplicado no banco de dados, todos os dados precisam ser extraídos na memória para aplicar o filtro no cliente. Com base no filtro e na quantidade de dados no servidor, a avaliação do cliente pode resultar em um desempenho ruim. Portanto, o Entity Framework Core bloqueia essa avaliação de cliente e gera uma exceção de runtime.

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

Avaliação explícita do cliente

Talvez seja necessário forçar a avaliação do cliente explicitamente em determinados casos, como a seguir

  • A quantidade de dados é pequena para que a avaliação no cliente não resulte em uma enorme penalidade de desempenho.
  • O operador LINQ que está sendo usado não tem nenhuma tradução do lado do servidor.

Nesses casos, você pode optar explicitamente pela avaliação do cliente chamando métodos como AsEnumerable ou ToList (AsAsyncEnumerable ou ToListAsync para assíncrono). Ao usar AsEnumerable você estaria transmitindo os resultados, mas usar ToList causaria o buffer criando uma lista, que também usa memória adicional. No entanto, se você estiver fazendo múltiplas enumerações, armazenar os resultados em uma lista será mais útil, pois o banco de dados só será consultado uma vez. Dependendo do uso específico, você deve avaliar qual método é mais útil para o caso.

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

Dica

Se você estiver usando AsAsyncEnumerable e quiser redigir a consulta ainda mais no lado do cliente, poderá usar a biblioteca System.Interactive.Async que define operadores para enumeráveis assíncronos. Para obter mais informações, confira operadores linq do lado do cliente.

Possível vazamento de memória na avaliação do cliente

Como a tradução e a compilação de consulta são caras, o EF Core armazena em cache o plano de consulta compilado. O delegado armazenado em cache pode usar o código do cliente ao fazer a avaliação do cliente da projeção de nível superior. O EF Core gera parâmetros para as partes avaliadas pelo cliente da árvore e reutiliza o plano de consulta substituindo os valores do parâmetro. Mas determinadas constantes na árvore de expressão não podem ser convertidas em parâmetros. Se o delegado armazenado em cache contiver essas constantes, esses objetos não poderão ser coletados como lixo, pois ainda estarão sendo referenciados. Se esse objeto contiver um DbContext ou outros serviços nele, ele poderá fazer com que o uso da memória do aplicativo cresça ao longo do tempo. Esse comportamento geralmente é um sinal de uma perda de memória. O EF Core gera uma exceção sempre que se depara com constantes de um tipo que não podem ser mapeadas usando o provedor de banco de dados atual. As causas comuns e suas soluções são as seguintes:

  • Usando um método de instância: ao usar métodos de instância em uma projeção de cliente, a árvore de expressão terá uma constante da instância. Se o método não usar dados da instância, considere tornar o método estático. Se você precisar de dados da instância no corpo do método, passe os dados específicos como um argumento para o método.
  • Passando argumentos constantes para o método: esse caso surge geralmente usando this em um argumento para o método cliente. Considere dividir o argumento em vários argumentos escalares, que podem ser mapeados pelo provedor de banco de dados.
  • Outras constantes: se uma constante for distribuída em qualquer outro caso, você poderá avaliar se a constante é necessária no processamento. Se for necessário ter a constante ou se você não puder usar uma solução nos casos acima, crie uma variável local para armazenar o valor e use a variável local na consulta. O EF Core converterá a variável local em um parâmetro.

Versões anteriores

A seção a seguir se aplica às versões do EF Core anterior a 3.0.

Versões mais antigas do EF Core dão suporte à avaliação do cliente em qualquer parte da consulta, não apenas na projeção de nível superior. É por isso que consultas semelhantes a uma postada na seção Avaliação do cliente sem suporte funcionaram corretamente. Como esse comportamento pode causar problemas de desempenho não notados, o EF Core registrou um aviso de avaliação do cliente. Para obter mais informações sobre como exibir a saída de log, confira Registrar em log.

Opcionalmente, o EF Core permitiu que você alterasse o comportamento padrão para gerar uma exceção ou não fazer nada ao fazer a avaliação do cliente (exceto na projeção). O comportamento de lançamento de exceção o tornaria semelhante ao comportamento em 3.0. Para alterar o comportamento, é necessário configurar avisos ao configurar as opções para o contexto, normalmente em DbContext.OnConfiguring ou em Startup.cs, se estiver usando o 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));
}