Bagikan melalui


Penelusuran Kesalahan Pelacak Perubahan

Pelacak perubahan Entity Framework Core (EF Core) menghasilkan dua jenis output untuk membantu penelusuran kesalahan:

  • memberikan ChangeTracker.DebugView tampilan yang dapat dibaca manusia dari semua entitas yang dilacak
  • Pesan log tingkat debug dihasilkan saat pelacak perubahan mendeteksi status dan memperbaiki hubungan

Tip

Dokumen ini mengasumsikan bahwa status entitas dan dasar-dasar pelacakan perubahan EF Core dipahami. Lihat Pelacakan Perubahan di EF Core untuk informasi selengkapnya tentang topik ini.

Tip

Anda dapat menjalankan dan men-debug ke semua kode dalam dokumen ini dengan mengunduh kode sampel dari GitHub.

Mengubah tampilan debug pelacak

Tampilan debug pelacak perubahan dapat diakses di debugger IDE Anda. Misalnya, dengan Visual Studio:

Mengakses tampilan debug pelacak perubahan dari debugger Visual Studio

Ini juga dapat diakses langsung dari kode, misalnya untuk mengirim tampilan debug ke konsol:

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

Tampilan debug memiliki bentuk pendek dan bentuk panjang. Formulir pendek menunjukkan entitas terlacak, statusnya, dan nilai kuncinya. Formulir panjang juga mencakup semua nilai dan status properti dan navigasi.

Tampilan pendek

Mari kita lihat contoh tampilan debug menggunakan model yang ditampilkan di akhir dokumen ini. Pertama, kami akan melacak beberapa entitas dan menempatkannya di beberapa status yang berbeda, sehingga kami memiliki data pelacakan perubahan yang baik untuk dilihat:

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

Mencetak tampilan pendek pada titik ini, seperti yang ditunjukkan di atas, menghasilkan output berikut:

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

Pemberitahuan:

  • Setiap entitas yang dilacak tercantum dengan nilai kunci primer (PK). Contohnya,Blog {Id: 1}.
  • Jika entitas adalah jenis entitas jenis bersama, maka jenis CLR juga ditampilkan. Contohnya,PostTag (Dictionary<string, object>).
  • selanjutnya EntityState ditunjukkan. Ini akan menjadi salah satu dari Unchanged, Added, Modified, atau Deleted.
  • Nilai untuk kunci alternatif (AK) ditampilkan berikutnya. Contohnya,AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Terakhir, nilai untuk setiap kunci asing (FK) ditampilkan. Contohnya,FK {PostsId: 4} FK {TagsId: 2}.

Tampilan panjang

Tampilan panjang dapat dikirim ke konsol dengan cara yang sama seperti tampilan singkat:

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

Output untuk status yang sama dengan tampilan singkat di atas adalah:

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

Setiap entitas terlacak dan statusnya ditampilkan seperti sebelumnya. Namun, tampilan panjang juga memperlihatkan nilai properti dan navigasi.

Nilai properti

Untuk setiap properti, tampilan panjang menunjukkan apakah properti adalah bagian dari kunci primer (PK), kunci alternatif (AK), atau kunci asing (FK) atau tidak. Contohnya:

  • Blog.Id adalah properti kunci utama: Id: 1 PK
  • Blog.AssetsId adalah properti kunci alternatif: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId adalah properti kunci asing: BlogId: 2 FK
  • BlogAssets.Id adalah kunci primer dan properti kunci asing: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

Nilai properti yang telah dimodifikasi ditandai seperti itu, dan nilai asli properti juga ditampilkan. Contohnya,Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Terakhir, Added entitas dengan nilai kunci sementara menunjukkan bahwa nilainya bersifat sementara. Contohnya,Id: -2147482643 PK Temporary.

Nilai navigasi ditampilkan menggunakan nilai kunci utama entitas yang direferensikan navigasi. Misalnya, dalam output di atas, posting 3 terkait dengan blog 2. Ini berarti bahwa Post.Blog navigasi menunjuk ke Blog instans dengan ID 2. Ini ditampilkan sebagai Blog: {Id: 2}.

Hal yang sama terjadi untuk navigasi pengumpulan, kecuali dalam hal ini mungkin ada beberapa entitas terkait. Misalnya, navigasi Blog.Posts koleksi berisi tiga entitas, dengan nilai kunci masing-masing 1, 2, dan -2147482643. Ini ditampilkan sebagai [{Id: 1}, {Id: 2}, {Id: -2147482643}].

Mengubah pengelogan pelacak

Pelacak perubahan mencatat pesan pada Debug LogLevel setiap kali mendeteksi properti atau navigasi berubah. Misalnya, ketika ChangeTracker.DetectChanges() dipanggil dalam kode di bagian atas dokumen ini dan pengelogan debug diaktifkan, maka log berikut dihasilkan:

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

Tabel berikut ini merangkum pesan pengelogan pelacak perubahan:

ID Peristiwa Deskripsi
CoreEventId.DetectChangesStarting DetectChanges() sedang dimulai
CoreEventId.DetectChangesCompleted DetectChanges() telah selesai
CoreEventId.PropertyChangeDetected Nilai properti normal telah berubah
CoreEventId.ForeignKeyChangeDetected Nilai properti kunci asing telah berubah
CoreEventId.CollectionChangeDetected Navigasi pengumpulan yang tidak dilewati telah menambahkan atau menghapus entitas terkait.
CoreEventId.ReferenceChangeDetected Navigasi referensi telah diubah untuk menunjuk ke entitas lain, atau diatur ke null
CoreEventId.StartedTracking EF Core mulai melacak entitas
CoreEventId.StateChanged Entitas EntityState telah berubah
CoreEventId.ValueGenerated Nilai dihasilkan untuk properti
CoreEventId.SkipCollectionChangeDetected Navigasi kumpulan lewati telah menambahkan atau menghapus entitas terkait

Model

Model yang digunakan untuk contoh di atas berisi jenis entitas berikut:

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
}

Model ini sebagian besar dikonfigurasi oleh konvensi, hanya dengan beberapa baris di 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);
}