Ladění sledování změn

Sledování změn Entity Framework Core (EF Core) generuje dva druhy výstupu, které vám pomůžou s laděním:

  • Poskytuje ChangeTracker.DebugView přehled o všech sledovaných entitách, které jsou čitelné pro člověka.
  • Zprávy protokolu na úrovni ladění se generují, když sledování změn zjistí stav a opraví relace.

Tip

Tento dokument předpokládá, že stavy entit a základy sledování změn EF Core jsou srozumitelné. Další informace o těchto tématech najdete v tématu Sledování změn v EF Core .

Tip

Celý kód v tomto dokumentu můžete spustit a ladit tak, že si stáhnete ukázkový kód z GitHubu.

Zobrazení ladění sledování změn

Zobrazení ladění sledování změn je přístupné v ladicím programu vašeho integrovaného vývojového prostředí (IDE). Například se sadou Visual Studio:

Přístup k zobrazení ladění sledování změn z ladicího programu sady Visual Studio

Můžete k němu přistupovat také přímo z kódu, například k odeslání zobrazení ladění do konzoly:

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

Zobrazení ladění má krátký formulář a dlouhý formulář. Krátký formulář zobrazuje sledované entity, jejich stav a klíčové hodnoty. Dlouhý formulář obsahuje také všechny vlastnosti a navigační hodnoty a stav.

Krátké zobrazení

Pojďme se podívat na příklad zobrazení ladění pomocí modelu zobrazeného na konci tohoto dokumentu. Nejprve budeme sledovat některé entity a vložit je do některých různých stavů, takže máme dobrá data sledování změn, která se mají zobrazit:

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

Tisk krátkého zobrazení v tomto okamžiku, jak je znázorněno výše, vede k následujícímu výstupu:

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

Poznámka:

  • Každá sledovaný entita je uvedená s hodnotou primárního klíče (PK). Například Blog {Id: 1}.
  • Pokud je entita typem entity sdíleného typu, zobrazí se také typ CLR. Například PostTag (Dictionary<string, object>).
  • Zobrazí se EntityState další. To bude jeden z Unchanged, Added, Modifiednebo Deleted.
  • Hodnoty pro všechny alternativní klíče (AKS) se zobrazí dále. Například AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Nakonec se zobrazí hodnoty pro všechny cizí klíče (FK). Například FK {PostsId: 4} FK {TagsId: 2}.

Dlouhé zobrazení

Dlouhé zobrazení lze odeslat do konzoly stejným způsobem jako krátké zobrazení:

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

Výstup pro stejný stav jako v krátkém zobrazení výše je:

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ždá sledovaný entita a její stav se zobrazí jako předtím. Dlouhé zobrazení ale zobrazuje také vlastnosti a navigační hodnoty.

Hodnoty vlastností

Dlouhé zobrazení pro každou vlastnost ukazuje, zda je vlastnost součástí primárního klíče (PK), alternativního klíče (AK) nebo cizího klíče (FK). Příklad:

  • Blog.Id je vlastnost primárního klíče: Id: 1 PK
  • Blog.AssetsId je alternativní vlastnost klíče: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId je vlastnost cizího klíče: BlogId: 2 FK
  • BlogAssets.Id je primární klíč i vlastnost cizího klíče: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

Hodnoty vlastností, které byly upraveny, jsou označeny jako takové a původní hodnota vlastnosti je zobrazena také. Například Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Nakonec entity s dočasnými hodnotami klíče označují, Added že hodnota je dočasná. Například Id: -2147482643 PK Temporary.

Navigační hodnoty se zobrazují pomocí hodnot primárního klíče entit, na které odkazují navigace. Například ve výstupu výše se příspěvek 3 vztahuje k blogu 2. To znamená, že Post.Blog navigační body na Blog instanci s ID 2. To se zobrazí jako Blog: {Id: 2}.

Totéž se děje u navigace v kolekcích, s tím rozdílem, že v tomto případě může existovat více souvisejících entit. Například navigace v kolekci Blog.Posts obsahuje tři entity s hodnotami klíče 1, 2 a -2147482643. To se zobrazí jako [{Id: 1}, {Id: 2}, {Id: -2147482643}].

Protokolování sledování změn

Sledování změn zaznamenává zprávy při Debug LogLevel každém zjištění vlastností nebo změn navigace. Pokud je například ChangeTracker.DetectChanges() volána v kódu v horní části tohoto dokumentu a je povolené protokolování ladění, vygenerují se následující protokoly:

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

Následující tabulka shrnuje zprávy protokolování sledování změn:

ID události Popis
CoreEventId.DetectChangesStarting DetectChanges() spouští se
CoreEventId.DetectChangesCompleted DetectChanges() dokončil(a)
CoreEventId.PropertyChangeDetected Změnila se normální hodnota vlastnosti.
CoreEventId.ForeignKeyChangeDetected Hodnota vlastnosti cizího klíče se změnila.
CoreEventId.CollectionChangeDetected Navigace v kolekci bez přeskočení obsahovala přidané nebo odebrané související entity.
CoreEventId.ReferenceChangeDetected Odkazová navigace byla změněna tak, aby odkazovala na jinou entitu, nebo byla nastavena na hodnotu null.
CoreEventId.StartedTracking Ef Core začala sledovat entitu
CoreEventId.StateChanged Entita EntityState se změnila.
CoreEventId.ValueGenerated Pro vlastnost se vygenerovala hodnota.
CoreEventId.SkipCollectionChangeDetected Navigace v kolekci přeskočení obsahovala přidané nebo odebrané související entity.

Model

Model použitý pro výše uvedené příklady obsahuje následující typy entit:

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 se většinou konfiguruje podle konvence s několika řádky v 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);
}