Partager via


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 :

Accès à la vue de débogage du suivi des modifications à partir du débogueur 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, Modifiedou Deleted.
  • 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

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);
}