Partager via


Accès aux entités suivies

Il existe quatre API principales pour accéder aux entités suivies par un DbContext :

Chacun de ces éléments est décrit en détail dans les sections suivantes.

Conseil

Ce document suppose que les états d’entité et les principes de base du suivi des modifications EF Core sont compris. Pour plus d’informations sur ces rubriques, consultez Suivi des modifications dans EF Core.

Conseil

Vous pouvez exécuter et déboguer dans tout le code de ce document en téléchargeant l’exemple de code à partir de GitHub.

Utilisation de DbContext.Entry et des instances EntityEntry

Pour chaque entité suivie, Entity Framework Core (EF Core) effectue le suivi des éléments suivants :

  • L’état général de l’entité, à savoir Unchanged, Modified, Added ou Deleted. Pour plus d’informations, consultez Suivi des modifications dans EF Core.
  • Les relations entre les entités suivies. Par exemple, le blog auquel appartient un billet.
  • Les valeurs actuelles des propriétés.
  • Les valeurs d’origine des propriétés, lorsque ces informations sont disponibles. Les valeurs d’origine sont les valeurs de propriété qui existaient lorsque l’entité était interrogée dans la base de données.
  • Les valeurs de propriété qui ont été modifiées depuis leur interrogation.
  • D’autres informations sur les valeurs de propriété, notamment si ces dernières sont ou non temporaires.

En transmettant une instance d’entité à DbContext.Entry, une instance EntityEntry<TEntity> fournit l’accès à ces informations pour l’entité donnée. Par exemple :

using var context = new BlogsContext();

var blog = context.Blogs.Single(e => e.Id == 1);
var entityEntry = context.Entry(blog);

Les sections suivantes montrent comment utiliser un élément EntityEntry pour accéder à l’état de l’entité ainsi qu’à celui des propriétés et des navigations de l’entité, et les manipuler.

Utilisation de l’entité

L’utilisation la plus courante d’EntityEntry<TEntity> consiste à accéder à l’état EntityState actuel d’une entité. Par exemple :

var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
    context.Entry(blog).State = EntityState.Modified;
}

La méthode Entry peut également être utilisée sur les entités qui ne sont pas encore suivies. Cela ne lance pas le suivi de l’entité ; l’état de l’entité est toujours Detached. Toutefois, l’élément EntityEntry retourné peut ensuite être utilisé pour modifier l’état de l’entité, qui sera alors suivie dans l’état donné. Par exemple, le code suivant lance le suivi d’une instance de blog en tant que Added :

var newBlog = new Blog();
Debug.Assert(context.Entry(newBlog).State == EntityState.Detached);

context.Entry(newBlog).State = EntityState.Added;
Debug.Assert(context.Entry(newBlog).State == EntityState.Added);

Conseil

Contrairement à EF6, la définition de l’état d’une entité individuelle n’entraîne pas le suivi de toutes les entités connectées. Le fait de définir l’état de cette manière est une opération de niveau inférieur à l’appel de Add, Attachou Update, qui fonctionnent sur un graphe entier d’entités.

Le tableau suivant récapitule les modes d’utilisation d’EntityEntry pour travailler avec une entité entière :

Membre EntityEntry Description
EntityEntry.State Obtient et définit l’EntityState de l’entité.
EntityEntry.Entity Obtient l’instance de l’entité.
EntityEntry.Context Le DbContext qui effectue le suivi de cette entité.
EntityEntry.Metadata Les métadonnées IEntityType pour le type d’entité.
EntityEntry.IsKeySet Indique si la valeur de la clé de l’entité a été ou non définie.
EntityEntry.Reload() Remplace les valeurs de propriété par des valeurs lues à partir de la base de données.
EntityEntry.DetectChanges() Force la détection des modifications pour cette entité uniquement ; consultez Détection et notifications des modifications.

Utilisation d’une seule propriété

Plusieurs surcharges d’EntityEntry<TEntity>.Property autorisent l’accès aux informations relatives à une propriété individuelle d’une entité. Par exemple, à l’aide d’une API fortement typée et fluide :

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);

Le nom de la propriété peut à la place être transmis sous forme de chaîne. Par exemple :

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");

La classe PropertyEntry<TEntity,TProperty> retournée peut ensuite être utilisée pour accéder aux informations relatives à la propriété. Par exemple, elle peut servir à obtenir et définir la valeur actuelle de la propriété sur cette entité :

string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";

Les deux méthodes Property utilisées ci-dessus retournent une instance PropertyEntry<TEntity,TProperty> générique fortement typée. L’utilisation de ce type générique est préférée, car elle autorise l’accès aux valeurs de propriété sans encadrer les types de valeurs. Toutefois, si le type d’entité ou de propriété n’est pas connu au moment de la compilation, une classe PropertyEntry non générique peut être obtenue à la place :

PropertyEntry propertyEntry = context.Entry(blog).Property("Name");

Cela permet d’accéder aux informations de n’importe quelle propriété quel que soit son type, au détriment de l’encadrement des types de valeurs. Par exemple :

object blog = context.Blogs.Single(e => e.Id == 1);

object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";

Le tableau suivant récapitule les informations de propriété exposées par PropertyEntry :

Membre PropertyEntry Description
PropertyEntry<TEntity,TProperty>.CurrentValue Obtient et définit la valeur actuelle de la propriété.
PropertyEntry<TEntity,TProperty>.OriginalValue Obtient et définit la valeur d’origine de la propriété, le cas échéant.
PropertyEntry<TEntity,TProperty>.EntityEntry Référence arrière à la classe EntityEntry<TEntity> de l’entité.
PropertyEntry.Metadata Métadonnées IProperty de la propriété.
PropertyEntry.IsModified Indique si cette propriété est marquée comme modifiée et permet de modifier cet état.
PropertyEntry.IsTemporary Indique si cette propriété est marquée comme temporaire et permet de modifier cet état.

Remarques :

  • La valeur d’origine d’une propriété est la valeur que la propriété avait lorsque l’entité a été interrogée à partir de la base de données. Toutefois, les valeurs d’origine ne sont pas disponibles si l’entité a été déconnectée, puis attachée explicitement à un autre DbContext, par exemple avec Attach ou Update. Dans ce cas, la valeur d’origine retournée est identique à la valeur actuelle.
  • SaveChanges met à jour uniquement les propriétés marquées comme modifiées. Définissez IsModified sur true pour forcer EF Core à mettre à jour une valeur de propriété donnée ou définissez cet élément sur false pour empêcher EF Core de mettre à jour la valeur de propriété.
  • Les valeurs temporaires sont souvent générées par les générateurs de valeurs EF Core. La définition de la valeur actuelle d’une propriété remplacera la valeur temporaire par la valeur donnée et marquera la propriété comme n’étant pas temporaire. Définissez IsTemporary sur true pour forcer une valeur à être temporaire même après sa définition explicite.

Utilisation d’une seule navigation

Plusieurs surcharges d’EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collection et EntityEntry.Navigation autorisent l’accès aux informations relatives à une navigation individuelle.

Les navigations de référence vers une entité associée unique sont accessibles via les méthodes Reference. Les navigations de référence pointent vers les côtés « un » des relations un-à-plusieurs, et les deux côtés des relations un-à-un. Par exemple :

ReferenceEntry<Post, Blog> referenceEntry1 = context.Entry(post).Reference(e => e.Blog);
ReferenceEntry<Post, Blog> referenceEntry2 = context.Entry(post).Reference<Blog>("Blog");
ReferenceEntry referenceEntry3 = context.Entry(post).Reference("Blog");

Les navigations peuvent également être des collections d’entités associées lorsqu’elles sont utilisées pour les côtés « plusieurs » des relations un-à-plusieurs et plusieurs-à-plusieurs. Les méthodes Collection sont utilisées pour accéder aux navigations de collection. Par exemple :

CollectionEntry<Blog, Post> collectionEntry1 = context.Entry(blog).Collection(e => e.Posts);
CollectionEntry<Blog, Post> collectionEntry2 = context.Entry(blog).Collection<Post>("Posts");
CollectionEntry collectionEntry3 = context.Entry(blog).Collection("Posts");

Certaines opérations sont communes à toutes les navigations. Ces informations sont accessibles à la fois pour les navigations de référence et de collection à l’aide de la méthode EntityEntry.Navigation. Notez que seul l’accès non générique est disponible lorsque vous accédez à toutes les navigations ensemble. Par exemple :

NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");

Le tableau suivant récapitule les modes d’utilisation de ReferenceEntry<TEntity,TProperty>, CollectionEntry<TEntity,TRelatedEntity> et NavigationEntry :

Membre NavigationEntry Description
MemberEntry.CurrentValue Obtient et définit la valeur actuelle de la navigation. Il s’agit de la collection entière pour les navigations de collection.
NavigationEntry.Metadata Métadonnées INavigationBase pour la navigation.
NavigationEntry.IsLoaded Obtient ou définit une valeur indiquant si l’entité ou la collection associée a été entièrement chargée à partir de la base de données.
NavigationEntry.Load() Charge l’entité ou la collection associée à partir de la base de données ; consultez Chargement explicite de données connexes.
NavigationEntry.Query() Requête utilisée par EF Core pour charger cette navigation en tant qu’interface IQueryable qui peut être composée ultérieurement ; consultez Chargement explicite de données connexes.

Utilisation de toutes les propriétés d’une entité

EntityEntry.Properties retourne une interface IEnumerable<T> de PropertyEntry pour chaque propriété de l’entité. Cela peut être utilisé pour effectuer une action pour chaque propriété de l’entité. Par exemple, pour définir une propriété DateTime sur DateTime.Now :

foreach (var propertyEntry in context.Entry(blog).Properties)
{
    if (propertyEntry.Metadata.ClrType == typeof(DateTime))
    {
        propertyEntry.CurrentValue = DateTime.Now;
    }
}

En outre, EntityEntry contient plusieurs méthodes pour obtenir et définir toutes les valeurs de propriété en même temps. Ces méthodes utilisent la classe PropertyValues, qui représente une collection de propriétés et leurs valeurs. PropertyValues peut être obtenu pour les valeurs actuelles ou d’origine, ou pour les valeurs actuellement stockées dans la base de données. Par exemple :

var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = context.Entry(blog).GetDatabaseValues();

Ces objets PropertyValues ne sont pas très utiles en soi. Toutefois, ils peuvent être combinés pour effectuer des opérations courantes nécessaires lors de la manipulation d’entités. Cela est utile lors de l’utilisation d’objets de transfert de données et lors de la résolution de conflits d’accès concurrentiel optimiste. Les sections suivantes vous montrent quelques exemples.

Définition de valeurs actuelles ou d’origine à partir d’une entité ou d’un objet de transfert de données

Les valeurs actuelles ou d’origine d’une entité peuvent être mises à jour en copiant des valeurs à partir d’un autre objet. Prenons l’exemple d’un objet de transfert de données BlogDto ayant les mêmes propriétés que le type d’entité :

public class BlogDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Vous pouvez vous en servir pour définir les valeurs actuelles d’une entité suivie en utilisant PropertyValues.SetValues :

var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDto);

Cette technique est parfois utilisée lors de la mise à jour d’une entité avec des valeurs obtenues à partir d’un appel de service ou d’un client dans une application multiniveau. Notez que l’objet utilisé n’a pas besoin d’être du même type que l’entité tant qu’il a des propriétés dont les noms correspondent à ceux de l’entité. Dans l’exemple ci-dessus, une instance de l’objet de transfert de données BlogDto est utilisée pour définir les valeurs actuelles d’une entité Blog suivie.

Notez que les propriétés ne seront marquées comme modifiées que si le jeu de valeurs diffère de la valeur actuelle.

Définition des valeurs actuelles ou d’origine à partir d’un dictionnaire

L’exemple précédent a défini des valeurs à partir d’une instance d’entité ou d’objet de transfert de données. Le même comportement est possible lorsque les valeurs de propriété sont stockées sous forme de paires nom/valeur dans un dictionnaire. Par exemple :

var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDictionary);

Définition des valeurs actuelles ou d’origine à partir de la base de données

Les valeurs actuelles ou d’origine d’une entité peuvent être mises à jour avec les dernières valeurs de la base de données en appelant GetDatabaseValues() ou GetDatabaseValuesAsync et en utilisant l’objet retourné pour définir les valeurs actuelles ou d’origine, ou les deux. Par exemple :

var databaseValues = context.Entry(blog).GetDatabaseValues();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);

Création d’un objet cloné contenant des valeurs actuelles, d’origine ou de base de données

L’objet PropertyValues retourné par CurrentValues, OriginalValues ou GetDatabaseValues peut être utilisé pour créer un clone de l’entité à l’aide de PropertyValues.ToObject(). Par exemple :

var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();

Notez que ToObject retourne une nouvelle instance qui n’est pas suivie par DbContext. L’objet retourné n’a pas non plus de relations définies sur d’autres entités.

L’objet cloné peut être utile pour résoudre les problèmes liés aux mises à jour simultanées de la base de données, en particulier lors de la liaison de données à des objets d’un certain type. Pour plus d’informations, consultez l’article relatif à l’accès concurrentiel optimiste.

Utilisation de toutes les navigations d’une entité

EntityEntry.Navigations retourne une interface IEnumerable<T> de NavigationEntry pour chaque navigation de l’entité. Les propriétés EntityEntry.References et EntityEntry.Collections effectuent les mêmes opérations, mais elles sont limités aux navigations de référence ou de collection respectivement. Cela peut être utilisé pour effectuer une action pour chaque navigation de l’entité. Par exemple, pour forcer le chargement de toutes les entités associées :

foreach (var navigationEntry in context.Entry(blog).Navigations)
{
    navigationEntry.Load();
}

Utilisation de tous les membres d’une entité

Les propriétés normales et de navigation ont un état et un comportement différents. Il est donc courant de traiter séparément les navigations et les non-navigations, comme indiqué dans les sections ci-dessus. Cependant, il peut parfois être utile de faire quelque chose avec n’importe quel membre de l’entité, qu’il s’agisse d’une propriété ou d’une navigation normale. EntityEntry.Member et EntityEntry.Members sont fournis à cet effet. Par exemple :

foreach (var memberEntry in context.Entry(blog).Members)
{
    Console.WriteLine(
        $"Member {memberEntry.Metadata.Name} is of type {memberEntry.Metadata.ClrType.ShortDisplayName()} and has value {memberEntry.CurrentValue}");
}

L’exécution de ce code sur un blog à partir de l’exemple génère la sortie suivante :

Member Id is of type int and has value 1
Member Name is of type string and has value .NET Blog
Member Posts is of type IList<Post> and has value System.Collections.Generic.List`1[Post]

Conseil

La vue de débogage de l’outil de suivi des modifications affiche des informations telles que celles-ci. La vue de débogage de l’ensemble du suivi des modifications est générée à partir de la propriété EntityEntry.DebugView individuelle de chaque entité suivie.

Find et FindAsync

DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Find et DbSet<TEntity>.FindAsync sont conçus pour une recherche efficace d’une seule entité lorsque sa clé primaire est connue. Find vérifie d’abord si l’entité est déjà suivie, et si tel est le cas, la retourne immédiatement. Une requête de base de données est effectuée uniquement si l’entité n’est pas suivie localement. Prenez par exemple ce code qui appelle Find Rechercher deux fois pour la même entité :

using var context = new BlogsContext();

Console.WriteLine("First call to Find...");
var blog1 = context.Blogs.Find(1);

Console.WriteLine($"...found blog {blog1.Name}");

Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = context.Blogs.Find(1);
Debug.Assert(blog1 == blog2);

Console.WriteLine("...returned the same instance without executing a query.");

La sortie de ce code (y compris la journalisation EF Core) lors de l’utilisation de SQLite est la suivante :

First call to Find...
info: 12/29/2020 07:45:53.682 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[@__p_0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
      SELECT "b"."Id", "b"."Name"
      FROM "Blogs" AS "b"
      WHERE "b"."Id" = @__p_0
      LIMIT 1
...found blog .NET Blog

Second call to Find...
...returned the same instance without executing a query.

Notez que le premier appel ne trouve pas l’entité localement et exécute donc une requête de base de données. À l’inverse, le deuxième appel retourne la même instance sans interroger la base de données, car elle est déjà suivie.

Find retourne null si une entité avec la clé donnée n’est pas suivie localement et n’existe pas dans la base de données.

Clés composites

Find peut également être utilisé avec des clés composites. Prenez par exemple une entité OrderLine avec une clé composite composée des ID de commande et de produit :

public class OrderLine
{
    public int OrderId { get; set; }
    public int ProductId { get; set; }

    //...
}

La clé composite doit être configurée dans DbContext.OnModelCreating pour définir les parties de la clé et leur ordre. Par exemple :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<OrderLine>()
        .HasKey(e => new { e.OrderId, e.ProductId });
}

Notez que OrderId constitue la première partie de la clé et ProductId la deuxième. Cet ordre doit être utilisé lors de la transmission de valeurs de clé à Find. Par exemple :

var orderline = context.OrderLines.Find(orderId, productId);

Utilisation de ChangeTracker.Entries pour accéder à toutes les entités suivies

Jusqu’à présent, nous n’avons accédé qu’à un seul EntityEntry à la fois. ChangeTracker.Entries() retourne un EntityEntry pour chaque entité actuellement suivie par DbContext. Par exemple :

using var context = new BlogsContext();
var blogs = context.Blogs.Include(e => e.Posts).ToList();

foreach (var entityEntry in context.ChangeTracker.Entries())
{
    Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}

Ce code génère la sortie suivante :

Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2

Notez que les entrées pour les blogs et les billets sont retournées. Les résultats peuvent être filtrés sur un type d’entité spécifique à l’aide de la surcharge générique ChangeTracker.Entries<TEntity>() :

foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

La sortie de ce code indique que seules les billets sont retournés :

Found Post entity with ID 1
Found Post entity with ID 2

En outre, l’utilisation de la surcharge générique retourne des instances EntityEntry<TEntity> génériques. C’est ce qui permet cet accès fluide à la propriété Id dans cet exemple.

Le type générique utilisé pour le filtrage n’a pas besoin d’être un type d’entité mappé ; un type ou une interface de base non mappé(e) peut être utilisé(e) à la place. Par exemple, si tous les types d’entités du modèle mettent en œuvre une interface définissant leur propriété clé :

public interface IEntityWithKey
{
    int Id { get; set; }
}

Cette interface peut alors être utilisée pour travailler avec la clé de n’importe quelle entité suivie d’une manière fortement typée. Par exemple :

foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

Utilisation de DbSet.Local pour interroger des entités suivies

Les requêtes EF Core sont toujours exécutées sur la base de données et retournent uniquement les entités qui ont été enregistrées dans la base de données. DbSet<TEntity>.Local fournit un mécanisme permettant d’interroger DbContext pour les entités locales et suivies.

Étant donné que DbSet.Local est utilisé pour interroger des entités suivies, il est courant de charger des entités dans DbContext, puis d’utiliser ces entités chargées. Cela est particulièrement vrai pour la liaison de données, mais peut également être utile dans d’autres situations. Par exemple, dans le code suivant, la base de données est d’abord interrogée pour tous les blogs et billets. La méthode d’extension Load est utilisée pour exécuter cette requête, les résultats étant suivis par le contexte sans être renvoyés directement à l’application. (L’utilisation de ToList ou d’une méthode similaire a le même effet, mais avec la surcharge de la création de la liste retournée, ce qui n’est pas nécessaire ici.) L’exemple utilise ensuite DbSet.Local pour accéder aux entités suivies localement :

using var context = new BlogsContext();

context.Blogs.Include(e => e.Posts).Load();

foreach (var blog in context.Blogs.Local)
{
    Console.WriteLine($"Blog: {blog.Name}");
}

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"Post: {post.Title}");
}

Notez que, contrairement à ChangeTracker.Entries(), DbSet.Local retourne directement des instances d’entité. Un EntityEntry peut, bien sûr, être obtenu pour l’entité retournée en appelant DbContext.Entry.

La vue locale

DbSet<TEntity>.Local retourne une vue des entités suivies localement qui reflète l’EntityState actuel de ces entités. Plus précisément, cela signifie que :

  • les entités Added sont incluses. Notez que ce n’est pas le cas pour les requêtes EF Core normales, car les entités Added n’existent pas encore dans la base de données et ne sont donc jamais retournées par une requête de base de données.
  • les entités Deleted sont exclues. Notez que ce n’est pas le cas pour les requêtes EF Core normales, car les entités Deleted existent toujours dans la base de données et sont donc retournées par les requêtes de base de données.

Tout cela signifie que DbSet.Local est une vue sur les données qui reflète l’état conceptuel actuel du graphique d’entité, avec des entités Added incluses et des entités Deleted exclues. Cela correspond à l’état de base de données attendu après l’appel de SaveChanges.

Il s’agit généralement de la vue idéale pour la liaison de données, car elle présente à l’utilisateur les données telles qu’elles le comprennent en fonction des modifications apportées par l’application.

Le code suivant illustre cela en marquant un billet comme Deleted, puis en ajoutant un nouveau billet, en le marquant comme Added :

using var context = new BlogsContext();

var posts = context.Posts.Include(e => e.Blog).ToList();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Remove(posts[1]);

context.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

La sortie de ce code est la suivante :

Local view after loading posts:
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing F# 5
  Post: Announcing .NET 5.0
Local view after adding and deleting posts:
  Post: What’s next for System.Text.Json?
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing .NET 5.0

Notez que le billet supprimé l’est de la vue locale et que le billet ajouté est inclus.

Utilisation de Local pour ajouter et supprimer des entités

DbSet<TEntity>.Local retourne une instance de LocalView<TEntity>. Il s’agit d’une implémentation d’ICollection<T> qui génère des notifications et y répond lorsque les entités sont ajoutées et supprimées de la collection. (Il s’agit du même concept qu’ObservableCollection<T>, mais implémenté en tant que projection sur les entrées de suivi des modifications EF Core existantes, plutôt que comme collection indépendante.)

Les notifications de la vue locale sont liées au suivi des modifications DbContext afin que la vue locale reste synchronisée avec DbContext. Plus précisément :

  • L’ajout d’une nouvelle entité à DbSet.Local provoque son suivi par DbContext, généralement dans l’état Added. (Si l’entité a déjà une valeur de clé générée, elle est suivie comme Unchanged à la place.)
  • La suppression d’une entité de DbSet.Local la marque comme Deleted.
  • Une entité qui devient suivie par DbContext apparaît automatiquement dans la collection DbSet.Local. Par exemple, l’exécution d’une requête pour importer automatiquement davantage d’entités entraîne la mise à jour de la vue locale.
  • Une entité marquée comme Deleted sera supprimée automatiquement de la collection locale.

Cela signifie que la vue locale peut être utilisée pour manipuler des entités suivies simplement en les ajoutant et en les supprimant de la collection. Par exemple, nous allons modifier l’exemple de code précédent pour ajouter et supprimer des billets dans la collection locale :

using var context = new BlogsContext();

var posts = context.Posts.Include(e => e.Blog).ToList();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Posts.Local.Remove(posts[1]);

context.Posts.Local.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

La sortie est identique à l’exemple précédent, car les modifications apportées à la vue locale sont synchronisées avec DbContext.

Utilisation de la vue locale pour la liaison de données Windows Forms ou WPF

DbSet<TEntity>.Local constitue la base de la liaison de données aux entités EF Core. Toutefois, Windows Forms et WPF fonctionnent le mieux lorsqu’ils sont utilisés avec le type spécifique der collection de notification qu’ils attendent. La vue locale prend en charge la création de ces types de collection spécifiques :

Par exemple :

ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();

Consultez Prise en main de WPF pour plus d’informations sur la liaison de données WPF avec EF Core et Prise en main de Windows Forms pour plus d’informations sur la liaison de données Windows Forms avec EF Core.

Conseil

La vue locale d’une instance DbSet donnée est créée de manière différée lors du premier accès, puis mise en cache. La création de LocalView est rapide et n’utilise pas beaucoup de mémoire. Toutefois, il appelle DetectChanges, ce qui peut être prend du temps avec un grand nombre d’entités. Les collections créées par ToObservableCollection et ToBindingList sont également créées de manière différée, puis mises en cache. Ces deux méthodes créent de nouvelles collections, ce qui peut prendre du temps et utiliser beaucoup de mémoire lorsque des milliers d’entités sont concernées.