Compartir a través de


Evaluación de cliente frente a servidor

Como regla general, Entity Framework Core intenta evaluar una consulta en el servidor tanto como sea posible. EF Core convierte partes de la consulta en parámetros, que se pueden evaluar en el lado cliente. El resto de la consulta (junto con los parámetros generados) se asigna al proveedor de base de datos para determinar la consulta de base de datos equivalente que se va a evaluar en el servidor. EF Core admite la evaluación parcial del cliente en la proyección de nivel superior (básicamente, la última llamada a Select()). Si la proyección de nivel superior de la consulta no se puede traducir al servidor, EF Core capturará los datos necesarios del servidor y evaluará las partes restantes de la consulta en el cliente. Si EF Core detecta una expresión, en cualquier lugar distinto de la proyección de nivel superior, que no se puede traducir al servidor, produce una excepción en tiempo de ejecución. Consulte Cómo funcionan las consultas para comprender cómo EF Core determina lo que no se puede traducir al servidor.

Nota:

Antes de la versión 3.0, Entity Framework Core admitía la evaluación de cliente en cualquier parte de la consulta. Para obtener más información, consulte la sección versiones anteriores.

Sugerencia

Puede ver un ejemplo de este artículo en GitHub.

Evaluación del cliente en la proyección de alto nivel

En el ejemplo siguiente, se usa un método auxiliar para estandarizar las direcciones URL de los blogs, que se devuelven de una base de datos de SQL Server. Dado que el proveedor de SQL Server no tiene información sobre cómo se implementa este método, no es posible traducirlo a SQL. Todos los demás aspectos de la consulta se evalúan en la base de datos, pero pasar lo devuelto URL a través de este método se lleva a cabo en el cliente.

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

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

    return url;
}

Evaluación de cliente no admitida

Aunque la evaluación de cliente es útil, puede dar lugar a un rendimiento deficiente a veces. Tenga en cuenta la siguiente consulta, en la que el método auxiliar se usa ahora en un filtro where. Dado que el filtro no se puede aplicar en la base de datos, todos los datos deben extraerse en la memoria para aplicar el filtro en el cliente. En función del filtro y la cantidad de datos en el servidor, la evaluación del cliente podría dar lugar a un rendimiento deficiente. Por lo tanto, Entity Framework Core bloquea dicha evaluación de cliente e inicia una excepción en tiempo de ejecución.

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

Evaluación de cliente explícita

Es posible que tenga que forzar la evaluación del cliente explícitamente en determinados casos, como seguir:

  • La cantidad de datos es pequeña para que la evaluación en el cliente no genere una enorme penalización de rendimiento.
  • El operador LINQ que se usa no tiene traducción del lado servidor.

En tales casos, puede elegir explícitamente la evaluación de cliente llamando a métodos como AsEnumerable o ToList (AsAsyncEnumerable o ToListAsync para operaciones asíncronas). Al usar AsEnumerable estarías transmitiendo los resultados, pero usar ToList provocaría almacenamiento en búfer al crear una lista, que también requiere memoria adicional. Aunque si va a enumerar varias veces, almacenar los resultados en una lista ayuda más, ya que solo hay una consulta en la base de datos. Dependiendo del uso determinado, debe evaluar qué método es más útil para el caso.

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

Sugerencia

Si usa AsAsyncEnumerable y desea continuar construyendo la consulta desde el lado del cliente, puede usar la biblioteca System.Interactive.Async que define operadores para enumerables asíncronos. Para obtener más información, consulte operadores linq del lado cliente.

Posible pérdida de memoria en la evaluación del cliente

Dado que la traducción y compilación de consultas son costosas, EF Core almacena en caché el plan de consulta compilado. El delegado almacenado en caché puede utilizar código del cliente mientras realiza la evaluación de la proyección de nivel superior. EF Core genera parámetros para las partes evaluadas por el cliente del árbol y reutiliza el plan de consulta reemplazando los valores de parámetro. Sin embargo, algunas constantes del árbol de expresiones no se pueden convertir en parámetros. Si el delegado almacenado en caché contiene estas constantes, esos objetos no se pueden recopilar como elementos no utilizados, ya que todavía se hace referencia a ellos. Si este tipo de objeto contiene un DbContext u otros servicios en él, podría provocar que el uso de memoria de la aplicación crezca con el tiempo. Este comportamiento suele ser un signo de pérdida de memoria. EF Core lanza una excepción cada vez que encuentra constantes de tipo que no se pueden mapear por el proveedor de base de datos actual. Las causas comunes y sus soluciones son las siguientes:

  • Uso de un método de instancia: al usar métodos de instancia en una proyección de cliente, el árbol de expresión contiene una constante de la instancia. Si el método no usa ningún dato de la instancia, considere la posibilidad de hacer que el método sea estático. Si necesita datos de instancia en el cuerpo del método, pase los datos específicos como argumento al método .
  • Pasar argumentos constantes al método: este caso surge generalmente mediante this en un argumento para el método cliente. Considere la posibilidad de dividir el argumento en varios argumentos escalares, que el proveedor de la base de datos pueda asignar.
  • Otras constantes: si una constante se encuentra en cualquier otro caso, puede evaluar si la constante es necesaria en el procesamiento. Si es necesario tener la constante o si no puede usar una solución de los casos anteriores, cree una variable local para almacenar el valor y use la variable local en la consulta. EF Core convertirá la variable local en un parámetro.

Versiones anteriores

La sección siguiente se aplica a las versiones de EF Core anteriores a la versión 3.0.

Las versiones anteriores de EF Core admitían la evaluación del cliente en cualquier parte de la consulta; no solo la proyección de nivel superior. Por eso las consultas similares a una publicada en la sección Evaluación de cliente no admitida funcionan correctamente. Dado que este comportamiento podría provocar problemas de rendimiento desapercibidos, EF Core registró una advertencia de evaluación de cliente. Para obtener más información sobre cómo ver la salida del registro, consulte Registro.

Opcionalmente, EF Core le permite cambiar el comportamiento predeterminado para iniciar una excepción o no hacer nada al realizar la evaluación de cliente (excepto en la proyección). El comportamiento de lanzamiento de excepciones sería similar al de la versión 3.0. Para cambiar el comportamiento, debe configurar advertencias al configurar las opciones del contexto, normalmente en DbContext.OnConfiguring, o en Startup.cs si usa 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));
}