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:
È 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
,Modified
oDeleted
. - 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
.
Valori di spostamento
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 Debug
LogLevel 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);
}