Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Chaque DbContext instance suit les modifications apportées aux entités. Ces entités suivies entraînent à leur tour les modifications apportées à la base de données lorsque SaveChanges est appelé. Cela est abordé dans Le suivi des modifications dans EF Core, et ce document suppose que les états d’entité et les principes de base du suivi des modifications d’Entity Framework Core (EF Core) sont compris.
Le suivi des modifications de propriété et de relation nécessite que DbContext puisse détecter ces modifications. Ce document explique comment cette détection se produit, ainsi que comment utiliser des notifications de propriétés ou des proxys de suivi des modifications pour forcer la détection immédiate des modifications.
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.
Suivi des modifications de capture instantanée
Par défaut, EF Core crée un instantané des valeurs de propriété de chaque entité lorsqu’elle est suivie pour la première fois par une instance DbContext. Les valeurs stockées dans cet instantané sont ensuite comparées aux valeurs actuelles de l’entité afin de déterminer les valeurs de propriété qui ont changé.
Cette détection des modifications se produit lorsque SaveChanges est appelé pour vous assurer que toutes les valeurs modifiées sont détectées avant d’envoyer des mises à jour à la base de données. Toutefois, la détection des modifications se produit également à d’autres moments pour s’assurer que l’application fonctionne avec des informations de suivi de up-to-date. À tout moment, vous pouvez forcer la détection des modifications en appelant ChangeTracker.DetectChanges().
Lorsque la détection des modifications est nécessaire
La détection des modifications est nécessaire lorsqu’une propriété ou une navigation a été modifiée sans utiliser EF Core pour apporter cette modification. Par exemple, envisagez de charger des blogs et des billets, puis d’apporter des modifications à ces entités :
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
L’analyse de la vue de débogage du suivi des modifications avant d’appeler ChangeTracker.DetectChanges() indique que les modifications apportées n’ont pas été détectées et ne sont donc pas reflétées dans les états d’entité et les données de propriété modifiées :
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog (Updated!)' Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, <not found>]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Plus précisément, l’état de l’entrée de blog est toujours Unchanged
, et le nouveau billet n’apparaît pas comme une entité suivie. (L’astute remarquera que les propriétés signalent leurs nouvelles valeurs, même si ces modifications n’ont pas encore été détectées par EF Core. Cela est dû au fait que la vue de débogage lit les valeurs actuelles directement à partir de l’instance d’entité.)
Contrastez-le avec la vue de débogage après avoir appelé DetectChanges :
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
Blog: {Id: 1}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Maintenant, le blog est correctement marqué comme Modified
et le nouveau billet a été détecté et est suivi comme Added
.
Au début de cette section, nous avons indiqué que la détection des modifications est nécessaire lorsque vous n’utilisez pas EF Core pour apporter la modification. C’est ce qui se passe dans le code ci-dessus. Autrement dit, les modifications apportées à la propriété et à la navigation sont effectuées directement sur les instances d’entité, et non à l’aide de méthodes EF Core.
Contrairement au code suivant qui modifie les entités de la même façon, mais cette fois à l’aide de méthodes EF Core :
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
context.Entry(blog).Property(e => e.Name).CurrentValue = ".NET Blog (Updated!)";
// Add a new entity to the DbContext
context.Add(
new Post
{
Blog = blog,
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Dans ce cas, la vue de débogage du suivi des modifications indique que tous les états d’entité et modifications de propriété sont connus, même si la détection des modifications n’a pas eu lieu. Cela est dû au fait qu’il PropertyEntry.CurrentValue s’agit d’une méthode EF Core, ce qui signifie qu’EF Core connaît immédiatement la modification apportée par cette méthode. De même, l’appel DbContext.Add permet à EF Core de connaître immédiatement la nouvelle entité et de la suivre de manière appropriée.
Conseil / Astuce
N’essayez pas d’éviter de détecter les modifications en utilisant toujours des méthodes EF Core pour apporter des modifications d’entité. Cela est souvent plus fastidieux et fonctionne moins bien que d’apporter des modifications aux entités de la manière normale. L’intention de ce document est d’informer quant au moment où la détection des modifications est nécessaire et quand ce n’est pas le cas. L’intention n’est pas d’encourager l’évitement de la détection des modifications.
Méthodes qui détectent automatiquement les modifications
DetectChanges() est appelé automatiquement par des méthodes où cela est susceptible d’avoir un impact sur les résultats. Ces méthodes sont les suivantes :
- DbContext.SaveChanges et DbContext.SaveChangesAsync, pour vous assurer que toutes les modifications sont détectées avant de mettre à jour la base de données.
- ChangeTracker.Entries() et ChangeTracker.Entries<TEntity>(), pour garantir que les états d’entité et les propriétés modifiées sont up-to-date.
- ChangeTracker.HasChanges(), pour vous assurer que le résultat est exact.
- ChangeTracker.CascadeChanges(), pour garantir les états d’entité corrects pour les entités principales/parents avant la cascade.
- DbSet<TEntity>.Local, pour vous assurer que le graphique suivi est up-to-date.
Il existe également certains endroits où la détection des modifications se produit sur une seule instance d’entité, plutôt que sur l’ensemble du graphique des entités suivies. Ces emplacements sont les suivants :
- Lorsque vous utilisez DbContext.Entry, pour vous assurer que les propriétés d’état et de modification de l’entité sont up-to-date.
- Lorsque vous utilisez EntityEntry des méthodes telles que
Property
,Collection
Reference
ouMember
pour garantir les modifications de propriété, les valeurs actuelles, etc. sont up-to-date. - Lorsqu’une entité dépendante/enfant va être supprimée, car une relation requise a été rompue. Cela détecte lorsqu’une entité ne doit pas être supprimée, car elle a été re-parentée.
La détection locale des modifications pour une seule entité peut être déclenchée explicitement en appelant EntityEntry.DetectChanges().
Remarque
Les modifications de détection locale peuvent manquer certaines modifications qu’une détection complète trouverait. Cela se produit lorsque des actions en cascade résultant de modifications non détectées apportées à d’autres entités ont un impact sur l’entité en question. Dans de telles situations, l’application peut avoir besoin de forcer une analyse complète de toutes les entités en appelant explicitement ChangeTracker.DetectChanges().
Désactivation de la détection automatique des modifications
Les performances de détection des modifications ne sont pas un goulot d’étranglement pour la plupart des applications. Toutefois, la détection des modifications peut devenir un problème de performances pour certaines applications qui suivent des milliers d’entités. (Le nombre exact dépend de plusieurs éléments, tels que le nombre de propriétés dans l’entité.) Pour cette raison, la détection automatique des modifications peut être désactivée à l’aide ChangeTracker.AutoDetectChangesEnabledde . Par exemple, envisagez de traiter les entités de jointure dans une relation plusieurs-à-plusieurs avec des charges utiles :
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entityEntry in ChangeTracker.Entries<PostTag>()) // Detects changes automatically
{
if (entityEntry.State == EntityState.Added)
{
entityEntry.Entity.TaggedBy = "ajcvickers";
entityEntry.Entity.TaggedOn = DateTime.Now;
}
}
try
{
ChangeTracker.AutoDetectChangesEnabled = false;
return await base.SaveChangesAsync(cancellationToken); // Avoid automatically detecting changes again here
}
finally
{
ChangeTracker.AutoDetectChangesEnabled = true;
}
}
Comme nous le savons dans la section précédente, ChangeTracker.Entries<TEntity>() et DbContext.SaveChanges détectent automatiquement les changements. Toutefois, après avoir appelé entrées, le code n’apporte aucune modification d’état d’entité ou de propriété. (La définition des valeurs de propriété normales sur les entités ajoutées n’entraîne aucune modification d’état.) Par conséquent, le code désactive la détection automatique inutile des modifications lors de l’appel dans la méthode SaveChanges de base. Le code utilise également un bloc try/finally pour s’assurer que le paramètre par défaut est restauré même si SaveChanges échoue.
Conseil / Astuce
Ne supposez pas que votre code doit désactiver la détection automatique des modifications pour fonctionner correctement. Cela n’est nécessaire que lorsque le profilage d'une application, qui suit de nombreuses entités, révèle que les performances de la détection des modifications posent un problème.
Détection des modifications et des conversions de valeurs
Pour utiliser le suivi d’instantané des modifications avec un type d’entité, EF Core doit être en mesure de :
- Créer un instantané de chaque valeur de propriété lorsque l’entité est suivie
- Comparez cette valeur à la valeur actuelle de la propriété
- Générer un code de hachage pour la valeur
Cela est géré automatiquement par EF Core pour les types qui peuvent être directement mappés à la base de données. Toutefois, lorsqu’un convertisseur de valeur est utilisé pour mapper une propriété, ce convertisseur doit spécifier comment effectuer ces actions. Cette opération est obtenue avec un comparateur de valeurs et est décrite en détail dans la documentation des comparateurs de valeurs .
Entités de notification
Le suivi des modifications d’instantané est recommandé pour la plupart des applications. Toutefois, les applications qui effectuent le suivi de nombreuses entités et/ou apportent de nombreuses modifications à ces entités peuvent tirer parti de l’implémentation d’entités qui notifient automatiquement EF Core lorsque leurs valeurs de propriété et de navigation changent. Elles sont appelées « entités de notification ».
Implémentation d’entités de notification
Les entités de notification utilisent les interfaces INotifyPropertyChanging et INotifyPropertyChanged, qui font partie de la bibliothèque de classes de base de .NET (BCL). Ces interfaces définissent les événements qui doivent être déclenchés avant et après avoir modifié une valeur de propriété. Par exemple:
public class Blog : INotifyPropertyChanging, INotifyPropertyChanged
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
public int Id
{
get => _id;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Id)));
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
private string _name;
public string Name
{
get => _name;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Name)));
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
En outre, toutes les navigations de collection doivent être implémentées INotifyCollectionChanged
; dans l'exemple ci-dessus, elles sont satisfaites à l'aide de ObservableCollection<T> postes. EF Core fournit également une ObservableHashSet<T> implémentation qui offre des recherches plus efficaces au détriment d'un ordre stable.
La plupart de ce code de notification est généralement déplacé dans une classe de base non mappée. Par exemple:
public class Blog : NotifyingEntity
{
private int _id;
public int Id
{
get => _id;
set => SetWithNotify(value, out _id);
}
private string _name;
public string Name
{
get => _name;
set => SetWithNotify(value, out _name);
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public abstract class NotifyingEntity : INotifyPropertyChanging, INotifyPropertyChanged
{
protected void SetWithNotify<T>(T value, out T field, [CallerMemberName] string propertyName = "")
{
NotifyChanging(propertyName);
field = value;
NotifyChanged(propertyName);
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void NotifyChanging(string propertyName)
=> PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}
Configuration des entités de notification
Il n’existe aucun moyen pour EF Core de vérifier que INotifyPropertyChanging
ou INotifyPropertyChanged
sont entièrement implémentés pour une utilisation avec EF Core. En particulier, certaines utilisations de ces interfaces le font avec des notifications uniquement sur certaines propriétés, plutôt que sur toutes les propriétés (y compris les navigations) comme requis par EF Core. Pour cette raison, EF Core ne se connecte pas automatiquement à ces événements.
Au lieu de cela, EF Core doit être configuré pour utiliser ces entités de notification. Cette opération est généralement effectuée pour tous les types d’entités en appelant ModelBuilder.HasChangeTrackingStrategy. Par exemple:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications);
}
(La stratégie peut également être définie différemment pour différents types d’entités à l’aide EntityTypeBuilder.HasChangeTrackingStrategy, mais cela est généralement contre-productif, car DetectChanges est toujours requis pour ces types qui ne sont pas des entités de notification.)
Le suivi complet des modifications de notification nécessite que les deux INotifyPropertyChanging
et INotifyPropertyChanged
soient implémentés. Cela permet aux valeurs d’origine d’être enregistrées juste avant la modification de la valeur de propriété, ce qui évite d’avoir besoin d’EF Core pour créer un instantané lors du suivi de l’entité. Les types d’entités qui implémentent uniquement INotifyPropertyChanged
peuvent également être utilisés avec EF Core. Dans ce cas, EF crée toujours un instantané lors du suivi d’une entité pour assurer le suivi des valeurs d’origine, mais utilise ensuite les notifications pour détecter immédiatement les modifications, plutôt que de devoir appeler DetectChanges.
Les différentes ChangeTrackingStrategy valeurs sont résumées dans le tableau suivant.
Stratégie de suivi des modifications | Interfaces nécessaires | Besoins de DetectChanges | Valeurs d'origine des captures instantanées |
---|---|---|---|
Instantané | Aucun | Oui | Oui |
NotificationsModifiées | INotifyPropertyChanged | Non | Oui |
NotificationsDeModificationEtModifié | INotifyPropertyChanged et INotifyPropertyChanging | Non | Non |
Notifications de modification et de modification terminée avec les valeurs d'origine | INotifyPropertyChanged et INotifyPropertyChanging | Non | Oui |
Utilisation d’entités de notification
Les entités de notification se comportent comme n’importe quelle autre entité, sauf que les modifications apportées aux instances d’entité ne nécessitent pas un appel à ChangeTracker.DetectChanges() pour détecter ces modifications. Par exemple:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Avec les entités normales, la vue de débogage du suivi des modifications indiquait que ces changements n'étaient pas détectés avant l'appel de DetectChanges. Les modifications sont détectées immédiatement dans la vue de débogage lorsque des entités de notification sont utilisées.
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
Blog: {Id: 1}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Proxys de suivi des modifications
EF Core peut générer dynamiquement des types de proxy qui implémentent INotifyPropertyChanging et INotifyPropertyChanged. Cela nécessite l’installation du package NuGet Microsoft.EntityFrameworkCore.Proxies et l’activation des proxys de suivi des modifications avec UseChangeTrackingProxies Par exemple :
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseChangeTrackingProxies();
La création d’un proxy dynamique implique la création d’un nouveau type .NET dynamique (à l’aide de l’implémentation de proxys Castle.Core ), qui hérite du type d’entité, puis remplace tous les setters de propriétés. Les types d’entités pour les proxys doivent donc être des types qui peuvent être hérités et doivent avoir des propriétés qui peuvent être substituées. En outre, les navigations de collection créées explicitement doivent implémenter INotifyCollectionChanged par exemple :
public class Blog
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public class Post
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
Un inconvénient important pour les proxys de suivi des modifications est que EF Core doit toujours suivre les instances du proxy, jamais les instances du type d’entité sous-jacent. Cela est dû au fait que les instances du type d’entité sous-jacent ne génèrent pas de notifications, ce qui signifie que les modifications apportées à ces entités sont manquées.
EF Core crée automatiquement des instances proxy lors de l’interrogation de la base de données. Cet inconvénient est donc généralement limité au suivi de nouvelles instances d’entité. Ces instances doivent être créées à l'aide des CreateProxy méthodes d'extension, et pas de la manière normale à l’aide de new
. Cela signifie que le code des exemples précédents doit maintenant utiliser CreateProxy
:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
context.CreateProxy<Post>(
p =>
{
p.Title = "What’s next for System.Text.Json?";
p.Content = ".NET 5.0 was released recently and has come with many...";
}));
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Événements de suivi des modifications
EF Core déclenche l’événement ChangeTracker.Tracked lorsqu’une entité est suivie pour la première fois. Les changements d’état d’entité futurs entraînent des ChangeTracker.StateChanged événements. Pour plus d’informations, consultez les événements .NET dans EF Core .
Remarque
L’événement StateChanged
n’est pas déclenché lorsqu’une entité est d’abord suivie, même si l’état est passé de Detached
à l’un des autres états. Assurez-vous d'écouter à la fois les événements StateChanged
et Tracked
pour recevoir toutes les notifications pertinentes.