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:
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
oderDeleted
. - 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
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 Debug
LogLevel, 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);
}