Partager via


Accès aux entités suivies

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

Chacune d’elles est décrite plus en détail dans les sections ci-dessous.

Conseil / Astuce

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 Change Tracking in EF Core .

Conseil / Astuce

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 des instances DbContext.Entry et EntityEntry

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

  • État global de l’entité. Il s'agit de l'un de Unchanged, Modified, Added, ou Deleted ; voir le suivi des modifications dans EF Core pour plus d'informations.
  • Relations entre les entités suivies. Par exemple, le blog auquel appartient un billet.
  • 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 quand l’entité a été interrogée à partir de la base de données.
  • Quelles valeurs de propriété ont été modifiées depuis qu’elles ont été interrogées.
  • D’autres informations sur les valeurs de propriété, telles que si la valeur est temporaire ou non.

Transmettre une instance d'entité à DbContext.Entry aboutit à un EntityEntry<TEntity> fournissant l'accès à ces informations pour l'entité donnée. Par exemple:

using var context = new BlogsContext();

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

Les sections suivantes montrent comment utiliser EntityEntry pour accéder à l’état d’entité et manipuler l’état de l’entité, ainsi que l’état des propriétés et des navigations de l’entité.

Travailler avec l'entité

L’utilisation EntityEntry<TEntity> la plus courante consiste à accéder à l’état actuel EntityState 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 démarre pas le suivi de l’entité ; l’état de l’entité est toujours Detached. Toutefois, l'EntityEntry retournée peut ensuite être utilisée pour modifier l’état de l’entité, moment auquel l’entité sera suivie dans l’état donné. Par exemple, le code suivant démarre le suivi d’une instance de blog sous forme de 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 / Astuce

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. Cela rend la définition de l’état de cette façon une opération de niveau inférieur à l’appel Add, Attachou Update, qui fonctionne sur un graphique entier d’entités.

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

Membre EntityEntry Descriptif
EntityEntry.State Obtient et définit l’entité EntityState .
EntityEntry.Entity Obtient l’instance d’entité.
EntityEntry.Context L'entité qui suit cette entité.
EntityEntry.Metadata IEntityType métadonnées pour le type d’entité.
EntityEntry.IsKeySet Indique si l’entité a ou non défini sa valeur de clé.
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 de EntityEntry<TEntity>.Property permettent d'accéder à des informations relatives à une propriété individuelle d’une entité. Par exemple, à l’aide d’une API fortement typée et de type fluide :

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

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

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

Le retour PropertyEntry<TEntity,TProperty> peut ensuite être utilisé pour accéder aux informations relatives à la propriété. Par exemple, il peut être utilisé pour 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 générique PropertyEntry<TEntity,TProperty> 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 emboîtement de types valeur. Toutefois, si le type d’entité ou de propriété n’est pas connu au moment de la compilation, un non générique PropertyEntry peut être obtenu à la place :

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

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

object blog = await context.Blogs.SingleAsync(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 Descriptif
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 inversée vers le EntityEntry<TEntity> de l’entité.
PropertyEntry.Metadata IProperty métadonnées 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 la valeur true pour forcer EF Core à mettre à jour une valeur de propriété donnée ou définissez-la IsModified sur false pour empêcher EF Core de mettre à jour la valeur de propriété.
  • Les valeurs temporaires sont généralement générées par des générateurs de valeurs EF Core. La définition de la valeur actuelle d’une propriété remplace la valeur temporaire par la valeur donnée et marque la propriété comme non temporaire. Définissez IsTemporary la valeur true pour forcer une valeur à être temporaire même après sa définition explicite.

Utilisation d’une navigation unique

Plusieurs surcharges de EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collectionet 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 Reference méthodes. Les navigations de référence pointent vers le côté « un » des relations un-à-plusieurs, ainsi que vers 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 aspects « nombreux » des relations un-à-plusieurs et plusieurs-à-plusieurs. Les Collection méthodes 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 courantes pour toutes les navigations. Ces informations sont accessibles à la fois pour les navigations de référence et de collection à l’aide de la EntityEntry.Navigation méthode. Notez que seul l’accès non générique est disponible lors de l’accès à toutes les navigations ensemble. Par exemple:

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

Le tableau suivant récapitule les façons d’utiliser ReferenceEntry<TEntity,TProperty>, CollectionEntry<TEntity,TRelatedEntity>et NavigationEntry:

Membre NavigationEntry Descriptif
MemberEntry.CurrentValue Obtient et définit la valeur actuelle de la navigation. Il s’agit de la collection entière pour la navigation des collections.
NavigationEntry.Metadata INavigationBase métadonnées 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 ; voir Chargement explicite des données associées.
NavigationEntry.Query() La requête qu'EF Core utiliserait pour charger cette navigation comme IQueryable que l'on peut composer plus en détail ; consultez Chargement explicite des données associées.

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

EntityEntry.Properties retourne une IEnumerable<T> valeur pour PropertyEntry 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 PropertyValues classe, 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 = await context.Entry(blog).GetDatabaseValuesAsync();

Ces objets PropertyValues ne sont pas très utiles eux-mêmes. Toutefois, elles peuvent être combinées 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 optimistes. Les sections suivantes vous montrent quelques exemples.

Définition des valeurs actuelles ou d'origine à partir d'une entité ou d'un DTO

Les valeurs actuelles ou d’origine d’une entité peuvent être mises à jour en copiant des valeurs à partir d’un autre objet. Par exemple, considérez un BlogDto objet de transfert de données (DTO) avec les mêmes propriétés que le type d’entité :

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

Cela peut être utilisé pour définir les valeurs actuelles d’une entité suivie à l’aide PropertyValues.SetValuesde :

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’elle a des propriétés dont les noms correspondent à ceux de l’entité. Dans l’exemple ci-dessus, une instance du DTO BlogDto est utilisée pour définir les valeurs actuelles d’une entité suivie Blog .

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 définit des valeurs à partir d’une entité ou d’une instance DTO. Le même comportement est disponible lorsque les valeurs de propriété sont stockées en tant que 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 en utilisant l’objet retourné pour définir les valeurs actuelles ou d’origine, ou les deux. Par exemple:

var databaseValues = await context.Entry(blog).GetDatabaseValuesAsync();
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 PropertyValues.ToObject()de . Par exemple:

var clonedBlog = (await context.Entry(blog).GetDatabaseValuesAsync()).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. Voir concurrence optimiste pour plus d’informations.

Utilisation de toutes les navigations d’une entité

EntityEntry.Navigations retourne une valeur IEnumerable<T> pour chaque NavigationEntry navigation de l’entité. EntityEntry.References et EntityEntry.Collections effectuent la même tâche, mais sont respectivement limités aux navigations de référence ou de collection. 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();
}

Travailler avec tous les membres d'une entité

Les propriétés régulières et les propriétés de navigation ont un état et un comportement différents. Il est donc courant de traiter les navigations et les non-navigations séparément, comme indiqué dans les sections ci-dessus. Toutefois, 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 régulière. 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 / Astuce

La vue de débogage du suivi des modifications affiche des informations telles que celles-ci. La vue de débogage de l’intégralité du traqueur de modifications est générée à partir de l’élément individuel EntityEntry.DebugView de chaque entité suivie.

Rechercher et RechercherAsynchrone

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

using var context = new BlogsContext();

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

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

Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = await context.Blogs.FindAsync(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.

Recherche 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

La recherche peut également être utilisée avec des clés composites. Par exemple, considérez une OrderLine entité avec une clé composite composée de l’ID de commande et de l’ID de produit :

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

    //...
}

La clé composite doit être configurée pour que les parties de la clé DbContext.OnModelCreating soient définis. Par exemple:

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

Notez qu’il OrderId s’agit de la première partie de la clé et ProductId de la deuxième partie de la clé. Cet ordre doit être utilisé lors de la transmission de valeurs clés à Trouver. Par exemple:

var orderline = await context.OrderLines.FindAsync(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 une entité EntityEntry pour chaque entité actuellement suivie par DbContext. Par exemple:

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

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 selon un type d'entité spécifique par l'utilisation 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 publications sont retournées :

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 génériques EntityEntry<TEntity> . C’est ce qui permet un accès de type fluent à 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 de base ou une interface non mappé peut être utilisé à la place. Par exemple, si tous les types d’entités du modèle implémentent une interface définissant leur propriété clé :

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

Ensuite, cette interface peut être utilisée pour utiliser la clé de n’importe quelle entité suivie de 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é qu’il DbSet.Local est utilisé pour interroger des entités suivies, il est courant de charger des entités dans DbContext, puis de travailler avec 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 interrogée pour tous les blogs et publications. La Load méthode d’extension est utilisée pour exécuter cette requête avec les résultats suivis par le contexte sans être retournés directement à l’application. (L’utilisation ToList ou similaire a le même effet, mais avec la surcharge de création de la liste retournée, ce qui n’est pas nécessaire ici.) L’exemple utilise DbSet.Local ensuite pour accéder aux entités suivies localement :

using var context = new BlogsContext();

await context.Blogs.Include(e => e.Posts).LoadAsync();

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 des instances d’entité directement. Un EntityEntry peut, bien sûr, toujours être obtenu pour l’entité retournée en appelant DbContext.Entry.

Vue locale

DbSet<TEntity>.Local retourne une vue des entités suivies localement qui reflète l'état actuel EntityState 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 Added les entités 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.
  • Deleted les entités sont exclues. Notez que ce n’est pas encore le cas pour les requêtes EF Core normales, car Deleted les entités 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 qu’il DbSet.Local s’agit d’une vue sur les données qui reflètent l’état conceptuel actuel du graphique d’entité, avec Added des entités incluses et Deleted des entités 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 = await context.Posts.Include(e => e.Blog).ToListAsync();

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é est supprimé de l’affichage local 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 de ICollection<T> qui génère et répond aux notifications lorsque des entités sont ajoutées et supprimées de la collection. (Il s’agit du même concept que ObservableCollection<T>, mais implémenté comme une projection sur les entrées existantes de suivi des modifications EF Core, plutôt que sous forme de collection indépendante.)

Les notifications de la vue locale sont connectées au suivi des modifications DbContext afin que la vue locale reste synchronisée avec DbContext. Spécifiquement:

  • L’ajout d’une nouvelle entité à DbSet.Local provoque son suivi par le 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 entraîne son marquage comme Deleted.
  • Une entité qui devient suivie par DbContext apparaît automatiquement dans la DbSet.Local collection. 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 étant supprimée automatiquement de la collection locale.

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

using var context = new BlogsContext();

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

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 reste inchangée par rapport à 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 de notification de collecte 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 bien démarrer avec Windows Forms pour plus d’informations sur la liaison de données Windows Forms avec EF Core.

Conseil / Astuce

La vue locale d’une instance DbSet donnée est créée de manière différée lors de l’accès, puis mise en cache. La création localView elle-même est rapide et n’utilise pas de mémoire significative. Toutefois, il appelle DetectChanges, qui peut être lent pour 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, qui peuvent être lentes et utiliser beaucoup de mémoire lorsque des milliers d’entités sont impliquées.