Debugowanie monitora zmian

Monitor zmian platformy Entity Framework Core (EF Core) generuje dwa rodzaje danych wyjściowych, które ułatwiają debugowanie:

  • Element ChangeTracker.DebugView zapewnia czytelny dla człowieka widok wszystkich śledzonych jednostek
  • Komunikaty dziennika na poziomie debugowania są generowane, gdy monitor zmian wykrywa stan i naprawia relacje

Napiwek

W tym dokumencie przyjęto założenie, że stany jednostki i podstawy śledzenia zmian platformy EF Core są zrozumiałe. Aby uzyskać więcej informacji na temat tych tematów, zobacz Change Tracking in EF Core (Śledzenie zmian w programie EF Core ).

Napiwek

Możesz uruchomić i debugować cały kod podany w tym dokumencie, pobierając przykładowy kod z serwisu GitHub.

Widok debugowania śledzenia zmian

Dostęp do widoku debugowania monitora zmian można uzyskać w debugerze środowiska IDE. Na przykład w programie Visual Studio:

Accessing the change tracker debug view from the Visual Studio debugger

Dostęp do niego można również uzyskać bezpośrednio z kodu, na przykład w celu wysłania widoku debugowania do konsoli:

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

Widok debugowania ma krótką formę i długi formularz. W krótkim formularzu przedstawiono śledzone jednostki, ich stan i wartości klucza. Formularz długi zawiera również wszystkie wartości właściwości i nawigacji oraz stan.

Krótki widok

Przyjrzyjmy się przykładowi widoku debugowania przy użyciu modelu pokazanego na końcu tego dokumentu. Najpierw prześledzimy niektóre jednostki i umieścimy je w różnych stanach, więc mamy dobre dane śledzenia zmian w celu wyświetlenia:

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

Drukowanie krótkiego widoku w tym momencie, jak pokazano powyżej, powoduje następujące dane wyjściowe:

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

Uwaga:

  • Każda śledzona jednostka jest wyświetlana z wartością klucza podstawowego (PK). Na przykład Blog {Id: 1}.
  • Jeśli jednostka jest typem jednostki typu współużytkowanego, jest również wyświetlany typ CLR. Na przykład PostTag (Dictionary<string, object>).
  • Zostanie EntityState wyświetlony następny. Będzie to jeden z Unchangedelementów , Added, Modifiedlub Deleted.
  • Wartości dla wszystkich kluczy alternatywnych (AKs) są wyświetlane obok. Na przykład AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Na koniec są wyświetlane wartości dla wszystkich kluczy obcych (FKs). Na przykład FK {PostsId: 4} FK {TagsId: 2}.

Widok długi

Długi widok można wysłać do konsoli w taki sam sposób jak w krótkim widoku:

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

Dane wyjściowe dla tego samego stanu co powyższy krótki widok to:

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

Każda śledzona jednostka i jej stan są wyświetlane tak jak poprzednio. Jednak widok długi pokazuje również wartości właściwości i nawigacji.

Wartości właściwości

Dla każdej właściwości widok długi pokazuje, czy właściwość jest częścią klucza podstawowego (PK), klucza alternatywnego (AK) lub klucza obcego (FK). Przykład:

  • Blog.Id jest właściwością klucza podstawowego: Id: 1 PK
  • Blog.AssetsId to właściwość klucza alternatywnego: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId jest właściwością klucza obcego: BlogId: 2 FK
  • BlogAssets.Id jest zarówno kluczem podstawowym, jak i właściwością klucza obcego: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

Wartości właściwości, które zostały zmodyfikowane, są oznaczone jako takie, a oryginalna wartość właściwości jest również wyświetlana. Na przykład Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Na koniec jednostki z wartościami klucza tymczasowego wskazują, Added że wartość jest tymczasowa. Na przykład Id: -2147482643 PK Temporary.

Wartości nawigacji są wyświetlane przy użyciu wartości klucza podstawowego jednostek, do których odwołuje się nawigacja. Na przykład w danych wyjściowych powyżej wpis 3 jest związany z blogem 2. Oznacza to, że nawigacja Post.Blog wskazuje wystąpienie o identyfikatorze Blog 2. Jest to wyświetlane jako Blog: {Id: 2}.

Dzieje się tak samo w przypadku nawigacji kolekcji, z tą różnicą, że w tym przypadku może istnieć wiele powiązanych jednostek. Na przykład nawigacja kolekcji Blog.Posts zawiera trzy jednostki z wartościami klucza 1, 2 i -2147482643. Jest to wyświetlane jako [{Id: 1}, {Id: 2}, {Id: -2147482643}].

Rejestrowanie monitora zmian

Monitor zmian rejestruje komunikaty przy DebugLogLevel każdym wykryciu zmian właściwości lub nawigacji. Na przykład po ChangeTracker.DetectChanges() wywołaniu w kodzie w górnej części tego dokumentu i włączeniu rejestrowania debugowania są generowane następujące dzienniki:

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

Poniższa tabela zawiera podsumowanie komunikatów rejestrowania monitora zmian:

Identyfikator zdarzenia opis
CoreEventId.DetectChangesStarting DetectChanges() jest uruchamiana
CoreEventId.DetectChangesCompleted DetectChanges() ukończono
CoreEventId.PropertyChangeDetected Zmieniono wartość właściwości normalnej
CoreEventId.ForeignKeyChangeDetected Zmieniono wartość właściwości klucza obcego
CoreEventId.CollectionChangeDetected Nawigacja po kolekcji bez pomijania ma dodane lub usunięte powiązane jednostki.
CoreEventId.ReferenceChangeDetected Nawigacja referencyjna została zmieniona tak, aby wskazywała inną jednostkę lub ustawiono wartość null
CoreEventId.StartedTracking Program EF Core rozpoczął śledzenie jednostki
CoreEventId.StateChanged Element EntityState jednostki uległ zmianie
CoreEventId.ValueGenerated Wartość została wygenerowana dla właściwości
CoreEventId.SkipCollectionChangeDetected Nawigacja pominięcia kolekcji miała dodane lub usunięte jednostki pokrewne

Model

Model używany w powyższych przykładach zawiera następujące typy jednostek:

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 jest w większości skonfigurowany zgodnie z konwencją, z zaledwie kilkoma wierszami w elem. 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);
}