Évaluation sur le client ou le serveur

Entity Framework Core tente en général d’évaluer autant que possible une requête sur le serveur. EF Core convertit des parties de la requête en paramètres qu’il peut évaluer côté client. Le reste de la requête (avec les paramètres générés) est fourni au fournisseur de base de données pour déterminer la requête de base de données équivalente à évaluer sur le serveur. EF Core prend en charge une évaluation partielle sur le client dans la projection de niveau supérieur (essentiellement, le dernier appel à Select()). Si la projection de niveau supérieur dans la requête ne peut pas être traduite sur le serveur, EF Core extrait toutes les données requises du serveur et évalue les parties restantes de la requête sur le client. Si EF Core détecte une expression qui ne peut pas être traduite sur le serveur, dans un emplacement autre que la projection de niveau supérieur, il lève une exception Runtime. Découvrez Fonctionnement des requêtes afin de comprendre comment EF Core détermine quel élément ne peut pas être traduit sur le serveur.

Remarque

Avant la version 3.0, Entity Framework Core prenait en charge une évaluation sur le client n’importe où dans la requête. Pour plus d’informations, consultez la section versions précédentes.

Conseil

Vous pouvez afficher cet exemple sur GitHub.

Évaluation sur le client dans la projection de niveau supérieur

Dans l’exemple suivant, une méthode d’assistance est utilisée pour normaliser les URL de blogs retournées à partir d’une base de données SQL Server. Le fournisseur SQL Server n’a aucune idée de la façon dont cette méthode est implémentée, il n’est pas possible de la traduire en SQL. Tous les autres aspects de la requête sont évalués dans la base de données, mais la transmission de la valeur URL retournée par cette méthode est effectuée sur le client.

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

Évaluation sur le client non prise en charge

Si une évaluation sur le client est utile, elle peut occasionner des performances médiocres. Prenez en compte la requête suivante, dans laquelle la méthode d’assistance est désormais utilisée dans un filtre emplacement. Étant donné que le filtre ne peut pas être appliqué dans la base de données, toutes les données doivent être extraites en mémoire pour appliquer le filtre au client. En fonction du filtre et de la quantité de données sur le serveur, une évaluation sur le client peut occasionner des performances médiocres. Ainsi Entity Framework Core bloque cette évaluation sur le client et lève une exception d’exécution.

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

Évaluation explicite sur le client

Vous allez peut-être devoir forcer une évaluation sur le client explicitement dans certains cas, comme les suivants

  • La quantité de données est faible, l’évaluation du client n’entraîne pas une baisse considérable des performances.
  • L’opérateur LINQ utilisé ne dispose d’aucune traduction côté serveur.

Dans ce cas, vous pouvez opter explicitement pour une évaluation sur le client en appelant des méthodes telles que AsEnumerable ou ToList (AsAsyncEnumerable ou ToListAsync pour async). En utilisant AsEnumerable, vous diffusez en continu les résultats, mais l’utilisation de ToList entraînerait une mise en mémoire tampon lors de la création d’une liste, ce qui prend également plus de mémoire. Toutefois, si vous énumérez plusieurs fois, alors le stockage des résultats dans une liste est plus utile, car il n’existe qu’une unique requête dans la base de données. En fonction de l’utilisation particulière, vous devez déterminer la méthode adaptée au cas.

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

Conseil

Si vous utilisez AsAsyncEnumerable et souhaitez composer la requête plus loin côté client, vous pouvez utiliser la bibliothèque System.Interactive.Async qui définit des opérateurs pour les énumérables asynchrones. Pour plus d’informations, consultez opérateurs LINQ côté client.

Fuite de mémoire potentielle dans une évaluation sur le client

Une traduction et une compilation des requêtes étant coûteuses, EF Core met en cache le plan de requête compilé. Le délégué mis en cache peut utiliser un code client lors d’une évaluation sur le client de la projection de niveau supérieur. EF Core génère des paramètres pour les parties évaluées du client de l’arborescence et réutilise le plan de requête en remplaçant les valeurs des paramètres. Certaines constantes de l’arborescence de l’expression ne peuvent ainsi pas être converties en paramètres. Si le délégué mis en cache contient des constantes, ces objets ne peuvent ainsi pas être récupérés dans la corbeille, car ils sont toujours référencés. Si un tel objet contient un DbContext ou d’autres services, cela peut entraîner une augmentation de l’utilisation de la mémoire de l’application au fil du temps. Ce comportement indique généralement une fuite de mémoire. EF Core lève une exception chaque fois qu’il s’agit des constantes d’un type ne pouvant être mappé à l’aide du fournisseur de base de données actuel. Les causes courantes et leurs solutions sont les suivantes :

  • Utilisation d’une méthode d’instance : lors de l’utilisation de méthodes d’instance dans une projection du client, l’arborescence de l’expression contient une constante de l’instance. Si votre méthode n’utilise aucune donnée de l’instance, envisagez de la rendre statique. Si vous avez besoin de données d’instance dans le corps de méthode, transmettez les données spécifiques en tant qu’argument à la méthode.
  • Transmission d’arguments constants à la méthode : ce cas se produit généralement au moyen de this dans un argument à la méthode du client. Envisagez un fractionnement de l’argument en plusieurs arguments scalaires pouvant être mappés par le fournisseur de base de données.
  • Autres constantes : si une constante se présente dans un autre cas, vous pouvez évaluer si elle est nécessaire dans le traitement. S’il est nécessaire d’avoir la constante ou si vous ne pouvez pas utiliser une solution provenant des cas ci-dessus, créez une variable locale pour stocker la valeur et utilisez-la dans la requête. EF Core va convertir la variable locale en paramètre.

Versions précédentes

La section suivante s’applique aux versions antérieures à 3.0 d’EF Core.

Des anciennes versions d’EF Core prises en charge pour une évaluation sur le client dans n’importe quelle partie de la requête, pas seulement dans la projection de niveau supérieur. C’est pourquoi les requêtes semblables à celles publiées dans la section Évaluation sur le client non pris en charge ont fonctionné correctement. Ce comportement pouvant occasionner des problèmes de performances invisibles, EF Core a journalisé un avertissement concernant une évaluation sur le client. Consultez Journalisation pour plus d’informations sur l’affichage de la sortie de la journalisation.

EF Core vous permet (au besoin) de modifier le comportement par défaut pour lever une exception ou ne rien faire lors d’une évaluation sur le client (à l’exception de dans la projection). Le comportement de la levée d’exception le rend similaire au comportement dans la version 3.0. Pour modifier le comportement, vous devez configurer des avertissements lors du paramétrage des options de votre contexte : généralement dans DbContext.OnConfiguring ou Startup.cs si vous utilisez 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));
}