Debuggen der Änderungsnachverfolgung

Die Entity Framework Core (EF Core)-Änderungsnachverfolgung generiert zwei Arten von Ausgaben, die beim Debuggen helfen:

  • Die ChangeTracker.DebugView stellt eine lesbare Ansicht aller Entitäten bereit, die nachverfolgt werden
  • Protokollmeldungen auf Debugebene werden generiert, wenn die Änderungsnachverfolgung Status erkennt und Beziehungen korrigiert.

Tipp

In diesem Dokument wird davon ausgegangen, dass Entitätszustände und die Grundlagen der EF Core-Änderungsnachverfolgung verstanden werden. Weitere Informationen zu diesen Themen finden Sie unter Änderungsnachverfolgung in EF Core.

Tipp

Sie können den gesamten Code in dieser Dokumentation ausführen und debuggen, indem Sie den Beispielcode von GitHub herunterladen.

Debugansicht der Änderungsnachverfolgung

Auf die Debugansicht der Änderungsnachverfolgung kann im Debugger Ihrer IDE zugegriffen werden. Beispielsweise mit Visual Studio:

Accessing the change tracker debug view from the Visual Studio debugger

Es kann auch direkt über den Code auf sie zugegriffen werden, um beispielsweise die Debugansicht an die Konsole zu senden:

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

Die Debugansicht verfügt über ein kurzes Formular und ein langes Formular. Das kurze Formular zeigt nachverfolgte Entitäten, deren Status und Schlüsselwerte an. Das lange Formular enthält auch alle Eigenschafts- und Navigationswerte und den Zustand.

Die kurze Ansicht

Sehen wir uns ein Debugansichtsbeispiel mit dem am Ende dieses Dokuments gezeigten Modell an. Zunächst werden wir einige Entitäten nachverfolgen und in verschiedene Zustände versetzen, so dass wir gute Änderungsnachverfolgungsdaten haben, um Folgendes anzuzeigen:

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

Wenn Sie die kurze Ansicht an diesem Punkt drucken, wie oben gezeigt, wird die folgende Ausgabe angezeigt:

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

Beachten Sie:

  • Jede nachverfolgte Entität wird mit dem Primärschlüsselwert (PK) aufgelistet. Beispiel: Blog {Id: 1}.
  • Wenn es sich bei der Entität um einen Entitätstyp vom Typ „Shared-Type“ handelt, wird auch der CLR-Typ angezeigt. Beispiel: PostTag (Dictionary<string, object>).
  • EntityState wird als Nächstes angezeigt. Dies wird Unchanged, Added, Modified oder Deleted.
  • Als Nächstes werden Werte für alternative Schlüssel (AKs) angezeigt. Beispiel: AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Schließlich werden Werte für alle Fremdschlüssel (FKs) angezeigt. Beispiel: FK {PostsId: 4} FK {TagsId: 2}.

Die lange Ansicht

Die lange Ansicht kann auf die gleiche Weise an die Konsole gesendet werden wie die kurze Ansicht:

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

Die Ausgabe für denselben Zustand wie die oben genannte kurze Ansicht lautet:

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

Jede nachverfolgte Entität und ihr Zustand werden wie zuvor angezeigt. Die lange Ansicht zeigt jedoch auch Eigenschafts- und Navigationswerte an.

Eigenschaftswerte

Für jede Eigenschaft zeigt die lange Ansicht an, ob die Eigenschaft Teil eines Primärschlüssels (PK), eines alternativen Schlüssels (AK) oder eines Fremdschlüssels (FK) ist. Beispiel:

  • Blog.Id ist eine Primärschlüsseleigenschaft: Id: 1 PK
  • Blog.AssetsId ist eine alternative Schlüsseleigenschaft: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId ist eine Fremdschlüsseleigenschaft: BlogId: 2 FK
  • BlogAssets.Id ist sowohl eine Primärschlüssel- als auch eine Fremdschlüsseleigenschaft: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

Eigenschaftswerte, die geändert wurden, werden als solche gekennzeichnet, und der ursprüngliche Wert der Eigenschaft wird ebenfalls angezeigt. Beispiel: Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Added-Entitäten mit temporären Schlüsselwerten geben an, dass der Wert temporär ist. Beispiel: Id: -2147482643 PK Temporary.

Navigationswerte werden mithilfe der Primärschlüsselwerte der Entitäten angezeigt, auf die die Navigation verweist. Beispielsweise bezieht sich der Beitrag 3 in der obigen Ausgabe auf Blog 2. Dies bedeutet, dass die Post.Blog-Navigation auf die Blog-Instanz mit der ID 2 verweist. Dies wird als Blog: {Id: 2} angezeigt.

Dasselbe geschieht für Sammlungsnavigationen, mit der Ausnahme, dass in diesem Fall mehrere verwandte Entitäten vorhanden sein können. Die Sammlungsnavigation „Blog.Posts“ enthält beispielsweise drei Entitäten mit den Schlüsselwerten 1, 2 und -2147482643. Dies wird als [{Id: 1}, {Id: 2}, {Id: -2147482643}] angezeigt.

Protokollierung der Änderungsverfolgung

Die Änderungsnachverfolgung protokolliert Nachrichten bei jeder DebugLogLevel, immer wenn sie Eigenschafts- oder Navigationsänderungen entdeckt. Wenn ChangeTracker.DetectChanges() beispielsweise im Code oben in diesem Dokument aufgerufen wird und die Debugprotokollierung aktiviert ist, werden die folgenden Protokolle generiert:

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

In der folgenden Tabelle werden die Nachrichten zur Änderungsverfolgungsprotokollierung zusammengefasst:

Ereignis-ID Beschreibung
CoreEventId.DetectChangesStarting DetectChanges() wird gestartet
CoreEventId.DetectChangesCompleted DetectChanges() ist abgeschlossen
CoreEventId.PropertyChangeDetected Ein normaler Eigenschaftswert wurde geändert.
CoreEventId.ForeignKeyChangeDetected Ein Fremdschlüsseleigenschaftswert wurde geändert.
CoreEventId.CollectionChangeDetected Eine nicht-überspringende Sammlungsnavigation hat verwandte Entitäten hinzugefügt oder entfernt.
CoreEventId.ReferenceChangeDetected Eine Referenznavigation wurde so geändert, dass sie auf eine andere Entität verweist oder auf NULL festgelegt wurde.
CoreEventId.StartedTracking EF Core hat mit der Nachverfolgung einer Entität begonnen.
CoreEventId.StateChanged Der EntityState einer Entität wurde geändert.
CoreEventId.ValueGenerated Für eine Eigenschaft wurde ein Wert generiert.
CoreEventId.SkipCollectionChangeDetected Eine Navigation mit überspringender Sammlung hat verwandte Entitäten hinzugefügt oder entfernt.

Das -Modell

Das für die obigen Beispiele verwendete Modell enthält die folgenden Entitätstypen:

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
}

Das Modell wurde hauptsächlich nach Konvention konfiguriert, mit nur wenigen Zeilen 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);
}