Débogage du dispositif de suivi des modifications
Le suivi des modifications Entity Framework Core (EF Core) génère deux types de sortie pour faciliter le débogage :
- La ChangeTracker.DebugView fournit une vue lisible par l’homme de toutes les entités suivies
- Les messages de journal au niveau du débogage sont générés lorsque le suivi des modifications détecte l’état et corrige les relations
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.
Vue de débogage du dispositif de suivi des modifications
La vue de débogage du suivi des modifications est accessible dans le débogueur de votre IDE. Par exemple, avec Visual Studio :
Elle est également accessible directement à partir du code, par exemple pour envoyer la vue de débogage à la console :
Console.WriteLine(context.ChangeTracker.DebugView.ShortView);
La vue de débogage a une forme courte et une forme longue. Le formulaire court affiche les entités suivies, leur état et leurs valeurs de clés. Le formulaire long inclut également toutes les valeurs de propriété et de navigation, ainsi que l’état.
La vue courte
Examinons un exemple d’affichage de débogage à l’aide du modèle affiché à la fin de ce document. Tout d’abord, nous allons suivre certaines entités et les placer dans certains états différents. Nous avons donc de bonnes données de suivi des modifications à afficher :
using var context = new BlogsContext();
var blogs = context.Blogs
.Include(e => e.Posts).ThenInclude(e => e.Tags)
.Include(e => e.Assets)
.ToList();
// Mark something Added
blogs[0].Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many new features and..."
});
// Mark something Deleted
blogs[1].Posts.Remove(blogs[1].Posts[1]);
// Make something Modified
blogs[0].Name = ".NET Blog (All new!)";
context.ChangeTracker.DetectChanges();
L’impression de la vue courte à ce stade, comme indiqué ci-dessus, entraîne la sortie suivante :
Blog {Id: 1} Modified AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}
Blog {Id: 2} Unchanged AK {AssetsId: 3a54b880-2b9d-486b-9403-dc2e52d36d65}
BlogAssets {Id: 3a54b880-2b9d-486b-9403-dc2e52d36d65} Unchanged FK {Id: 3a54b880-2b9d-486b-9403-dc2e52d36d65}
BlogAssets {Id: ed727978-1ffe-4709-baee-73913e8e44a0} Unchanged FK {Id: ed727978-1ffe-4709-baee-73913e8e44a0}
Post {Id: -2147482643} Added FK {BlogId: 1}
Post {Id: 1} Unchanged FK {BlogId: 1}
Post {Id: 2} Unchanged FK {BlogId: 1}
Post {Id: 3} Unchanged FK {BlogId: 2}
Post {Id: 4} Deleted FK {BlogId: 2}
PostTag (Dictionary<string, object>) {PostsId: 1, TagsId: 1} Unchanged FK {PostsId: 1} FK {TagsId: 1}
PostTag (Dictionary<string, object>) {PostsId: 1, TagsId: 3} Unchanged FK {PostsId: 1} FK {TagsId: 3}
PostTag (Dictionary<string, object>) {PostsId: 2, TagsId: 1} Unchanged FK {PostsId: 2} FK {TagsId: 1}
PostTag (Dictionary<string, object>) {PostsId: 3, TagsId: 2} Unchanged FK {PostsId: 3} FK {TagsId: 2}
PostTag (Dictionary<string, object>) {PostsId: 4, TagsId: 2} Deleted FK {PostsId: 4} FK {TagsId: 2}
Tag {Id: 1} Unchanged
Tag {Id: 2} Unchanged
Tag {Id: 3} Unchanged
Avis :
- Chaque entité suivie est répertoriée avec sa valeur de clé primaire (PK). Par exemple,
Blog {Id: 1}
- Si l’entité est un type d’entité de type partagé, le type CLR est également affiché. Par exemple,
PostTag (Dictionary<string, object>)
- La EntityState s’affiche ensuite. Il s’agit de l’une des valeurs suivantes :
Unchanged
,Added
,Modified
ouDeleted
. - Les valeurs des clés alternatives (AK) s’affichent ensuite. Par exemple,
AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}
- Enfin, les valeurs des clés étrangères (FK) s’affichent. Par exemple,
FK {PostsId: 4} FK {TagsId: 2}
La vue longue
La vue longue peut être envoyée à la console de la même façon que la vue courte :
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
La sortie pour le même état que la vue courte ci-dessus est la suivante :
Blog {Id: 1} Modified
Id: 1 PK
AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'
Assets: {Id: ed727978-1ffe-4709-baee-73913e8e44a0}
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Blog {Id: 2} Unchanged
Id: 2 PK
AssetsId: '3a54b880-2b9d-486b-9403-dc2e52d36d65' AK
Name: 'Visual Studio Blog'
Assets: {Id: 3a54b880-2b9d-486b-9403-dc2e52d36d65}
Posts: [{Id: 3}]
BlogAssets {Id: 3a54b880-2b9d-486b-9403-dc2e52d36d65} Unchanged
Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK
Banner: <null>
Blog: {Id: 2}
BlogAssets {Id: ed727978-1ffe-4709-baee-73913e8e44a0} Unchanged
Id: 'ed727978-1ffe-4709-baee-73913e8e44a0' PK FK
Banner: <null>
Blog: {Id: 1}
Post {Id: -2147482643} Added
Id: -2147482643 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many new fe...'
Title: 'What's next for System.Text.Json?'
Blog: {Id: 1}
Tags: []
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}
Tags: [{Id: 1}, {Id: 3}]
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}
Tags: [{Id: 1}]
Post {Id: 3} Unchanged
Id: 3 PK
BlogId: 2 FK
Content: 'If you are focused on squeezing out the last bits of perform...'
Title: 'Disassembly improvements for optimized managed debugging'
Blog: {Id: 2}
Tags: [{Id: 2}]
Post {Id: 4} Deleted
Id: 4 PK
BlogId: 2 FK
Content: 'Examine when database queries were executed and measure how ...'
Title: 'Database Profiling with Visual Studio'
Blog: <null>
Tags: [{Id: 2}]
PostTag (Dictionary<string, object>) {PostsId: 1, TagsId: 1} Unchanged
PostsId: 1 PK FK
TagsId: 1 PK FK
PostTag (Dictionary<string, object>) {PostsId: 1, TagsId: 3} Unchanged
PostsId: 1 PK FK
TagsId: 3 PK FK
PostTag (Dictionary<string, object>) {PostsId: 2, TagsId: 1} Unchanged
PostsId: 2 PK FK
TagsId: 1 PK FK
PostTag (Dictionary<string, object>) {PostsId: 3, TagsId: 2} Unchanged
PostsId: 3 PK FK
TagsId: 2 PK FK
PostTag (Dictionary<string, object>) {PostsId: 4, TagsId: 2} Deleted
PostsId: 4 PK FK
TagsId: 2 PK FK
Tag {Id: 1} Unchanged
Id: 1 PK
Text: '.NET'
Posts: [{Id: 1}, {Id: 2}]
Tag {Id: 2} Unchanged
Id: 2 PK
Text: 'Visual Studio'
Posts: [{Id: 3}, {Id: 4}]
Tag {Id: 3} Unchanged
Id: 3 PK
Text: 'EF Core'
Posts: [{Id: 1}]
Chaque entité suivie et son état sont affichés comme avant. Toutefois, la vue longue affiche également les valeurs de propriété et de navigation.
Valeurs de propriétés
Pour chaque propriété, la vue longue indique si la propriété fait partie ou non d’une clé primaire (PK), d’une clé secondaire (AK) ou d’une clé étrangère (FK). Par exemple :
Blog.Id
est une propriété de clé primaire :Id: 1 PK
Blog.AssetsId
est une autre propriété de clé :AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
Post.BlogId
est une propriété de clé étrangère :BlogId: 2 FK
BlogAssets.Id
est à la fois une clé primaire et une propriété de clé étrangère :Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK
Les valeurs de propriété qui ont été modifiées sont marquées comme telles, et la valeur d’origine de la propriété est également affichée. Par exemple, Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'
Enfin, les entités Added
avec des valeurs de clés temporaires indiquent que la valeur est temporaire. Par exemple, Id: -2147482643 PK Temporary
Valeurs de navigation
Les valeurs de navigation sont affichées à l’aide des valeurs de clés primaires des entités référencées par les navigations. Par exemple, dans la sortie ci-dessus, le billet 3 est lié au blog 2. Cela signifie que la navigation Post.Blog
pointe vers l’instance de Blog
avec l’ID 2. Il s’agit de Blog: {Id: 2}
.
La même chose se produit pour les navigations de collection, sauf que dans ce cas il peut y avoir plusieurs entités associées. Par exemple, la navigation de collection Blog.Posts
contient trois entités, avec les valeurs de clé 1, 2 et -2147482643 respectivement. Il s’agit de [{Id: 1}, {Id: 2}, {Id: -2147482643}]
.
Enregistrement du dispositif de suivi des modifications
Le dispositif de suivi des modifications enregistre des messages dans Debug
LogLevel chaque fois qu’il détecte des modifications de propriété ou de navigation. Par exemple, lorsque ChangeTracker.DetectChanges() est appelé dans le code en haut de ce document et que l’enregistrement du débogage est activé, les journaux suivants sont générés :
dbug: 12/30/2020 13:52:44.815 CoreEventId.DetectChangesStarting[10800] (Microsoft.EntityFrameworkCore.ChangeTracking)
DetectChanges starting for 'BlogsContext'.
dbug: 12/30/2020 13:52:44.818 CoreEventId.PropertyChangeDetected[10802] (Microsoft.EntityFrameworkCore.ChangeTracking)
The unchanged property 'Blog.Name' was detected as changed from '.NET Blog' to '.NET Blog (All new!)' and will be marked as modified for entity with key '{Id: 1}'.
dbug: 12/30/2020 13:52:44.820 CoreEventId.StateChanged[10807] (Microsoft.EntityFrameworkCore.ChangeTracking)
The 'Blog' entity with key '{Id: 1}' tracked by 'BlogsContext' changed state from 'Unchanged' to 'Modified'.
dbug: 12/30/2020 13:52:44.821 CoreEventId.CollectionChangeDetected[10804] (Microsoft.EntityFrameworkCore.ChangeTracking)
1 entities were added and 0 entities were removed from navigation 'Blog.Posts' on entity with key '{Id: 1}'.
dbug: 12/30/2020 13:52:44.822 CoreEventId.ValueGenerated[10808] (Microsoft.EntityFrameworkCore.ChangeTracking)
'BlogsContext' generated temporary value '-2147482638' for the property 'Id.Post'.
dbug: 12/30/2020 13:52:44.822 CoreEventId.StartedTracking[10806] (Microsoft.EntityFrameworkCore.ChangeTracking)
Context 'BlogsContext' started tracking 'Post' entity with key '{Id: -2147482638}'.
dbug: 12/30/2020 13:52:44.827 CoreEventId.CollectionChangeDetected[10804] (Microsoft.EntityFrameworkCore.ChangeTracking)
0 entities were added and 1 entities were removed from navigation 'Blog.Posts' on entity with key '{Id: 2}'.
dbug: 12/30/2020 13:52:44.827 CoreEventId.StateChanged[10807] (Microsoft.EntityFrameworkCore.ChangeTracking)
The 'Post' entity with key '{Id: 4}' tracked by 'BlogsContext' changed state from 'Unchanged' to 'Modified'.
dbug: 12/30/2020 13:52:44.829 CoreEventId.CascadeDeleteOrphan[10003] (Microsoft.EntityFrameworkCore.Update)
An entity of type 'Post' with key '{Id: 4}' changed to 'Deleted' state due to severed required relationship to its parent entity of type 'Blog'.
dbug: 12/30/2020 13:52:44.829 CoreEventId.StateChanged[10807] (Microsoft.EntityFrameworkCore.ChangeTracking)
The 'Post' entity with key '{Id: 4}' tracked by 'BlogsContext' changed state from 'Modified' to 'Deleted'.
dbug: 12/30/2020 13:52:44.829 CoreEventId.CollectionChangeDetected[10804] (Microsoft.EntityFrameworkCore.ChangeTracking)
0 entities were added and 1 entities were removed from navigation 'Blog.Posts' on entity with key '{Id: 2}'.
dbug: 12/30/2020 13:52:44.831 CoreEventId.CascadeDelete[10002] (Microsoft.EntityFrameworkCore.Update)
A cascade state change of an entity of type 'PostTag' with key '{PostsId: 4, TagsId: 2}' to 'Deleted' occurred due to the deletion of its parent entity of type 'Post' with key '{Id: 4}'.
dbug: 12/30/2020 13:52:44.831 CoreEventId.StateChanged[10807] (Microsoft.EntityFrameworkCore.ChangeTracking)
The 'PostTag' entity with key '{PostsId: 4, TagsId: 2}' tracked by 'BlogsContext' changed state from 'Unchanged' to 'Deleted'.
dbug: 12/30/2020 13:52:44.831 CoreEventId.DetectChangesCompleted[10801] (Microsoft.EntityFrameworkCore.ChangeTracking)
DetectChanges completed for 'BlogsContext'.
Le tableau suivant résume les messages de journalisation du suivi des modifications :
ID de l’événement | Description |
---|---|
CoreEventId.DetectChangesStarting | DetectChanges() est en cours de démarrage |
CoreEventId.DetectChangesCompleted | DetectChanges() est terminée |
CoreEventId.PropertyChangeDetected | Une valeur de propriété normale a été modifiée |
CoreEventId.ForeignKeyChangeDetected | La valeur de propriété d’une clé étrangère a été modifiée |
CoreEventId.CollectionChangeDetected | Une navigation de collection non ignorée a eu des entités associées ajoutées ou supprimées. |
CoreEventId.ReferenceChangeDetected | Une navigation de référence a été modifiée pour pointer vers une autre entité, ou définie sur Null |
CoreEventId.StartedTracking | EF Core a démarré le suivi d’une entité |
CoreEventId.StateChanged | La EntityState d’une entité a changé |
CoreEventId.ValueGenerated | Une valeur a été générée pour une propriété |
CoreEventId.SkipCollectionChangeDetected | Une navigation de collection ignorée a eu des entités associées ajoutées ou supprimées |
Modèle
Le modèle utilisé pour les exemples ci-dessus contient les types d’entités suivants :
public class Blog
{
public int Id { get; set; } // Primary key
public Guid AssetsId { get; set; } // Alternate key
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>(); // Collection navigation
public BlogAssets Assets { get; set; } // Reference navigation
}
public class BlogAssets
{
public Guid Id { get; set; } // Primary key and foreign key
public byte[] Banner { get; set; }
public Blog Blog { get; set; } // Reference navigation
}
public class Post
{
public int Id { get; set; } // Primary key
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; } // Foreign key
public Blog Blog { get; set; } // Reference navigation
public IList<Tag> Tags { get; } = new List<Tag>(); // Skip collection navigation
}
public class Tag
{
public int Id { get; set; } // Primary key
public string Text { get; set; }
public IList<Post> Posts { get; } = new List<Post>(); // Skip collection navigation
}
Le modèle est principalement configuré par convention, avec seulement quelques lignes dans OnModelCreating :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Blog>()
.Property(e => e.AssetsId)
.ValueGeneratedOnAdd();
modelBuilder
.Entity<BlogAssets>()
.HasOne(e => e.Blog)
.WithOne(e => e.Assets)
.HasForeignKey<BlogAssets>(e => e.Id)
.HasPrincipalKey<Blog>(e => e.AssetsId);
}