更改跟踪器调试
Entity Framework Core (EF Core) 更改跟踪器会生成两种类型的输出以帮助进行调试:
- ChangeTracker.DebugView 提供了有关要跟踪的所有实体的直观易懂的视图
- 更改跟踪器检测到状态并修复关系时,将生成调试级别日志消息
提示
本文档假设你已了解实体状态和 EF Core 更改跟踪的基础知识。 有关这些主题的详细信息,请参阅 EF Core 中的更改跟踪。
提示
通过从 GitHub 下载示例代码,你可运行并调试到本文档中的所有代码。
更改跟踪器调试视图
可以在 IDE 的调试器中访问更改跟踪器调试视图。 例如,在 Visual Studio 中:
也可以直接通过代码访问它,例如,将调试视图发送到控制台:
Console.WriteLine(context.ChangeTracker.DebugView.ShortView);
调试视图具有短视图形式和长视图形式。 短视图形式显示跟踪的实体、它们的状态和键值。 长视图形式还包括所有属性以及导航值和状态。
短视图
我们来查看使用本文档末尾所示的模型进行的一个调试视图示例。 首先,我们将跟踪一些实体,并使它们处于不同的状态,以便可以查看良好的更改跟踪数据:
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();
如上所示,此时打印短视图将产生以下输出:
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
注意:
- 每个跟踪实体都列出了其主键 (PK) 值。 例如
Blog {Id: 1}
。 - 如果实体是共享类型实体类型,则还会显示其 CLR 类型。 例如
PostTag (Dictionary<string, object>)
。 - 接下来会显示 EntityState。 这将是以下状态之一:
Unchanged
、Added
、Modified
或Deleted
。 - 接下来将显示任何替代键 (AK) 的值。 例如
AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}
。 - 最后,将显示任何外键 (FK) 的值。 例如
FK {PostsId: 4} FK {TagsId: 2}
。
长视图
可以通过与短视图相同的方式,将长视图发送到控制台:
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
与上文中短视图相同的状态的输出为:
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}]
每个跟踪的实体及其状态都将按原来的方式显示。 但是,长视图还会显示属性和导航值。
属性值
对于每个属性,长视图会显示属性是否是主键 (PK)、替代键 (AK) 或外键 (FK) 的一部分。 例如:
Blog.Id
是主键属性:Id: 1 PK
Blog.AssetsId
是替代键属性:AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
Post.BlogId
是外键属性:BlogId: 2 FK
BlogAssets.Id
既是主键又是外键属性:Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK
修改过的属性值都将以此方式进行标记,并且还会显示属性的原始值。 例如 Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'
。
最后,具有临时键值的 Added
实体将表明该值是临时值。 例如 Id: -2147482643 PK Temporary
。
导航值
导航值是使用导航引用的实体的主键值显示的。 例如,在上面的输出中,post 3 与 blog 2 相关。 这意味着 Post.Blog
导航指向 ID 为 2 的 Blog
实例。 这可以表示为 Blog: {Id: 2}
。
集合导航也发生相同的情况,只是在这种情况下,可能有多个相关实体。 例如,集合导航 Blog.Posts
包含三个实体,它们的键值分别为 1、2 和 -2147482643。 这可以表示为 [{Id: 1}, {Id: 2}, {Id: -2147482643}]
。
更改跟踪器日志记录
每当检测到属性或导航更改时,更改跟踪器会在 Debug
LogLevel 记录消息。 例如,在本文档顶部的代码中调用 ChangeTracker.DetectChanges() 并启用调试日志记录时,将生成以下日志:
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'.
下表汇总了更改跟踪器日志记录消息:
事件 ID | 说明 |
---|---|
CoreEventId.DetectChangesStarting | DetectChanges() 正在启动 |
CoreEventId.DetectChangesCompleted | DetectChanges() 已完成 |
CoreEventId.PropertyChangeDetected | 常规属性值已更改 |
CoreEventId.ForeignKeyChangeDetected | 外键属性值已更改 |
CoreEventId.CollectionChangeDetected | 非跳过集合导航已添加或删除相关实体。 |
CoreEventId.ReferenceChangeDetected | 引用导航已更改为指向另一个实体,或设置为 null |
CoreEventId.StartedTracking | EF Core 已开始跟踪实体 |
CoreEventId.StateChanged | 实体的 EntityState 已更改 |
CoreEventId.ValueGenerated | 已为属性生成了值 |
CoreEventId.SkipCollectionChangeDetected | 跳过集合导航已添加或删除相关实体 |
模型
用于上述示例的模型包含以下实体类型:
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
}
模型主要通过约定配置,在 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);
}