변경 내용 추적기 디버깅
EF Core(Entity Framework 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
.
탐색 값
탐색 값은 탐색에서 참조하는 엔터티의 기본 키 값을 사용하여 표시됩니다. 예를 들어 위의 출력에서 게시물 3은 블로그 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);
}
.NET