Debug di Change Tracker

Entity Framework Core (EF Core) change tracker genera due tipi di output per facilitare il debug:

  • ChangeTracker.DebugView fornisce una visualizzazione leggibile di tutte le entità monitorate
  • I messaggi di log a livello di debug vengono generati quando lo strumento di rilevamento delle modifiche rileva lo stato e corregge le relazioni

Suggerimento

Questo documento presuppone che gli stati dell'entità e le nozioni di base del rilevamento delle modifiche di EF Core siano compresi. Per altre informazioni su questi argomenti, vedere Rilevamento modifiche in EF Core.

Suggerimento

È possibile eseguire ed eseguire il debug in tutto il codice di questo documento scaricando il codice di esempio da GitHub.

Visualizzazione debug rilevamento modifiche

È possibile accedere alla visualizzazione debug di Rilevamento modifiche nel debugger dell'IDE. Ad esempio, con Visual Studio:

Accessing the change tracker debug view from the Visual Studio debugger

È anche possibile accedervi direttamente dal codice, ad esempio per inviare la visualizzazione di debug alla console:

Console.WriteLine(context.ChangeTracker.DebugView.ShortView);

La visualizzazione di debug ha una forma breve e una maschera lunga. La forma breve mostra le entità rilevate, il relativo stato e i valori di chiave. Il formato lungo include anche tutti i valori di proprietà e di navigazione e lo stato.

La vista breve

Si esaminerà ora un esempio di visualizzazione di debug usando il modello illustrato alla fine di questo documento. Prima di tutto, verranno rilevate alcune entità e inserite in alcuni stati diversi, quindi sono disponibili buoni dati di rilevamento delle modifiche da visualizzare:

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

La stampa della visualizzazione breve in questo punto, come illustrato in precedenza, comporta l'output seguente:

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

Avviso:

  • Ogni entità rilevata viene elencata con il relativo valore di chiave primaria (PK). Ad esempio, Blog {Id: 1}.
  • Se l'entità è un tipo di entità di tipo condiviso, viene visualizzato anche il tipo CLR. Ad esempio, PostTag (Dictionary<string, object>).
  • L'oggetto EntityState viene visualizzato di seguito. Sarà uno di Unchanged, Added, Modifiedo Deleted.
  • I valori per le chiavi alternative (AKS) vengono visualizzati successivamente. Ad esempio, AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Vengono infine visualizzati i valori per tutte le chiavi esterne (FK). Ad esempio, FK {PostsId: 4} FK {TagsId: 2}.

Visualizzazione lunga

La visualizzazione lunga può essere inviata alla console nello stesso modo della visualizzazione breve:

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

L'output per lo stesso stato della visualizzazione breve precedente è:

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}]

Ogni entità rilevata e il relativo stato vengono visualizzati come in precedenza. Tuttavia, la visualizzazione lunga mostra anche i valori di proprietà e navigazione.

Valori delle proprietà

Per ogni proprietà, la visualizzazione lunga indica se la proprietà fa parte di una chiave primaria (PK), di una chiave alternativa (AK) o di una chiave esterna (FK). Ad esempio:

  • Blog.Id è una proprietà della chiave primaria: Id: 1 PK
  • Blog.AssetsId è una proprietà di chiave alternativa: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId è una proprietà di chiave esterna: BlogId: 2 FK
  • BlogAssets.Id è sia una chiave primaria che una proprietà di chiave esterna: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

I valori delle proprietà modificati vengono contrassegnati come tali e viene visualizzato anche il valore originale della proprietà. Ad esempio, Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Infine, Added le entità con valori di chiave temporanea indicano che il valore è temporaneo. Ad esempio, Id: -2147482643 PK Temporary.

I valori di spostamento vengono visualizzati usando i valori di chiave primaria delle entità a cui fa riferimento il riquadro di spostamento. Nell'output precedente, ad esempio, il post 3 è correlato al blog 2. Ciò significa che lo Post.Blog spostamento punta all'istanza Blog con ID 2. Viene visualizzato come Blog: {Id: 2}.

La stessa cosa accade per gli spostamenti della raccolta, ad eccezione del fatto che in questo caso possono essere presenti più entità correlate. Ad esempio, lo spostamento Blog.Posts della raccolta contiene tre entità, rispettivamente con i valori chiave 1, 2 e -2147482643. Viene visualizzato come [{Id: 1}, {Id: 2}, {Id: -2147482643}].

Registrazione rilevamento modifiche

Lo strumento di rilevamento modifiche registra i messaggi in corrispondenza di DebugLogLevel ogni volta che rileva modifiche alla proprietà o allo spostamento. Ad esempio, quando ChangeTracker.DetectChanges() viene chiamato nel codice nella parte superiore di questo documento e la registrazione di debug è abilitata, vengono generati i log seguenti:

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'.

La tabella seguente riepiloga i messaggi di registrazione del rilevamento modifiche:

ID evento Descrizione
CoreEventId.DetectChangesStarting DetectChanges() è in avvio
CoreEventId.DetectChangesCompleted DetectChanges() completato
CoreEventId.PropertyChangeDetected Un valore normale della proprietà è stato modificato
CoreEventId.ForeignKeyChangeDetected Un valore della proprietà di chiave esterna è stato modificato
CoreEventId.CollectionChangeDetected Una struttura di spostamento di una raccolta non skip include entità correlate aggiunte o rimosse.
CoreEventId.ReferenceChangeDetected Una navigazione di riferimento è stata modificata in modo che punti a un'altra entità o impostata su Null
CoreEventId.StartedTracking EF Core ha avviato il rilevamento di un'entità
CoreEventId.StateChanged L'oggetto EntityState di un'entità è stato modificato
CoreEventId.ValueGenerated È stato generato un valore per una proprietà
CoreEventId.SkipCollectionChangeDetected Una navigazione ignora raccolta ha aggiunto o rimosso entità correlate

Il modello

Il modello usato per gli esempi precedenti contiene i tipi di entità seguenti:

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
}

Il modello è configurato principalmente per convenzione, con poche righe in 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);
}