Requêtes avec suivi ou sans suivi

Le comportement du suivi consiste à contrôler si Entity Framework Core conserve des informations concernant une instance d’entité dans son suivi des modifications. Si une entité est suivie, toutes les modifications détectées dans l’entité sont répertoriées dans la base de données pendant SaveChanges. EF Core corrige également les propriétés de navigation entre les entités se trouvant dans un résultat de requête de suivi et celles qui sont dans le suivi des modifications.

Remarque

Les types d’entités sans clé ne sont jamais suivis. Chaque fois que cet article mentionne les types d’entités, il fait référence aux types d’entités qui ont une clé définie.

Conseil

Vous pouvez afficher cet exemple sur GitHub.

Requêtes avec suivi

Par défaut, les requêtes qui retournent des types d’entités ont le suivi activé. Une requête de suivi signifie que toutes les modifications apportées aux instances d’entité sont conservées par SaveChanges. Dans l’exemple suivant, la modification de l’évaluation des blogs est détectée et répertoriée dans la base de données pendant SaveChanges :

var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
blog.Rating = 5;
context.SaveChanges();

Lorsque les résultats sont renvoyés dans une requête de suivi, EF Core vérifie si l’entité est déjà dans le contexte d’exécution. Si EF Core trouve une entité existante, la même instance est alors retournée, ce qui peut potentiellement utiliser moins de mémoire et être plus rapide qu’une requête sans suivi. EF Core ne remplace pas les valeurs actuelles et d’origine des propriétés de l’entité dans l’entrée par les valeurs de la base de données. Si l’entité est introuvable dans le contexte d’exécution, EF Core crée une instance d’entité et l’attache au contexte. Les résultats de la requête ne contiennent aucune entité ajoutée au contexte tant qu’elle n’est pas encore enregistrée dans la base de données.

Pas de suivi des requêtes

Les requêtes sans suivi sont utiles lorsque les résultats sont utilisés dans un scénario en lecture seule. Leur exécution est généralement plus rapide, car il n’est pas nécessaire de configurer les informations de suivi des modifications. Si les entités récupérées à partir de la base de données n’ont pas besoin d’être mises à jour, une requête sans suivi doit alors être utilisée. Une requête individuelle peut être définie pour être non suivie. Une requête sans suivi donne également des résultats basés sur les éléments de la base de données et ignore les modifications locales ou les entités ajoutées.

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();

Le comportement de suivi par défaut peut être modifié au niveau de l’instance de contexte d'exécution :

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var blogs = context.Blogs.ToList();

La section suivante explique dans quel cas une requête sans suivi peut être moins efficace qu’une requête de suivi.

Résolution de l'identité

Étant donné qu’une requête de suivi utilise le suivi des modifications, EF Core effectue la résolution d’identités dans une requête de suivi. Lors de la matérialisation d’une entité, EF Core retourne la même instance d’entité à partir du suivi des modifications si celle-ci est déjà suivie. Si le résultat contient la même entité plusieurs fois, la même instance est alors retournée pour chaque occurrence. Les requêtes sans suivi :

  • N’utilisent pas le suivi des modifications et n’effectuent pas de résolution d’identité.
  • Renvoient une nouvelle instance de l’entité même lorsque la même entité est contenue dans le résultat plusieurs fois.

Les modes suivi et non suivi peuvent être combinés dans la même requête. Autrement dit, vous pouvez avoir une requête sans suivi qui effectue la résolution d’identité dans les résultats. Tout comme l’opérateur interrogeable AsNoTracking, nous avons ajouté un autre opérateur AsNoTrackingWithIdentityResolution<TEntity>(IQueryable<TEntity>). Il existe également une entrée associée ajoutée dans l’énumération QueryTrackingBehavior. Lorsque la requête utilisée pour la résolution d’identité est configurée sans suivi, un suivi des modifications autonome est utilisé en arrière-plan pour générer les résultats de la requête, de sorte que chaque instance ne se matérialise qu’une seule fois. Étant donné que ce suivi des modifications est différent de celui du contexte d’exécution, les résultats ne sont pas suivis par le contexte. Une fois la requête énumérée entièrement, le suivi des modifications devient hors de portée et collecté par conséquent dans les éléments supprimés.

var blogs = context.Blogs
    .AsNoTrackingWithIdentityResolution()
    .ToList();

Configuration du comportement de suivi par défaut

Si vous devez changer le comportement de suivi pour de nombreuses requêtes, vous pouvez modifier la valeur par défaut à la place :

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFQuerying.Tracking;Trusted_Connection=True")
        .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}

Toutes vos requêtes seront alors non suivies par défaut. Vous pouvez toujours ajouter AsTracking pour effectuer un suivi des requêtes spécifique.

Suivi et personnalisation des projections

Même si le type de résultat de la requête n’est pas un type d’entité, EF Core effectue toujours le suivi par défaut des types d’entités contenus dans le résultat. Dans la requête suivante, qui retourne un type anonyme, les instances de Blog dans le jeu de résultats sont suivies.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, PostCount = b.Posts.Count() });

Si l’ensemble des résultats contient des types d’entités provenant de la composition LINQ, EF Core les suivra.

var blog = context.Blogs
    .Select(
        b =>
            new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });

Si l’ensemble des résultats ne contient aucun type d’entité, aucun suivi ne sera effectué. Dans la requête suivante, nous renvoyons un type anonyme avec des valeurs contenues dans l’entité (mais aucune instance du type d’entité réel). Aucune entité suivie ne ressort de cette requête.

var blog = context.Blogs
    .Select(
        b =>
            new { Id = b.BlogId, b.Url });

EF Core prend en charge l’évaluation des clients dans la projection de niveau élevé. Si EF Core matérialise une instance d’entité pour l’évaluation du client, elle sera alors suivie. Ici, étant donné que nous transmettons des entités blog à la méthode StandardizeURL du client, EF Core effectue également le suivi des instances de blog.

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

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

    return url;
}

EF Core n’effectue pas de suivi pour les instances d’entité sans clé contenues dans le résultat. En revanche, il prend en charge le suivi de toutes les autres instances de types d’entités ayant une clé, en fonction des règles ci-dessus.

Versions précédentes

Avant la version 3.0, le suivi effectué par EF Core comportait des différences. Voici les différences notables :

  • Comme expliqué dans la page Client ou Évaluation du serveur, avant la version 3.0, EF Core prenait charge l’évaluation du client dans n’importe quelle partie de la requête. L’évaluation du client provoquait la matérialisation d’entités qui ne faisaient pas partie du résultat. EF Core analysait donc le résultat pour détecter les éléments à suivre. Cette conception contenait certaines variantes telles que :

    • L’évaluation du client dans la projection, qui provoquait la matérialisation, mais ne renvoyait pas l’instance d’entité matérialisée et n’était pas suivie. Dans l’exemple suivant, les entités blog n’étaient pas suivies.

      var blogs = context.Blogs
          .OrderByDescending(blog => blog.Rating)
          .Select(
              blog => new { Id = blog.BlogId, Url = StandardizeUrl(blog) })
          .ToList();
      
    • EF Core n’effectuait pas le suivi des objets sortis de la composition LINQ dans certains cas. Dans l’exemple suivant, Postn’était pas suivi.

      var blog = context.Blogs
          .Select(
              b =>
                  new { Blog = b, Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault() });
      
  • Chaque fois que les résultats de la requête contenaient des types d’entités sans clé, l’ensemble de la requête était effectué sans suivi. Cela signifie que les types d’entités ayant des clés et présents dans le résultat n’étaient pas suivis non plus.

  • EF Core effectuait les résolution d’identités dans les requêtes sans suivi. Il utilisait des références faibles pour suivre les entités qui avaient déjà été retournées. Par conséquent, si un ensemble de résultats contenait plusieurs fois la même entité, vous obteniez la même instance pour chaque occurrence. Cependant si un résultat précédent avait la même identité mais avait été mis hors de portée et collecté dans les éléments supprimés, EF Core renvoyait une nouvelle instance.