Megosztás:


Változáskövető hibakeresés

Az Entity Framework Core (EF Core) változáskövetője kétféle kimenetet hoz létre a hibakereséshez:

  • Az ChangeTracker.DebugView egy emberi olvasási formátumot biztosít az összes nyomon követett elemhez.
  • Hibakeresési szintű naplóüzenetek akkor jönnek létre, amikor a változáskövető észleli az állapotot, és kijavítja a kapcsolatokat

Jótanács

Ez a dokumentum feltételezi, hogy az entitásállapotok és az EF Core-változáskövetés alapjai érthetőek. Ezekről a témakörökről további információt az EF Core változáskövetési funkciójában talál.

Jótanács

A mintakód GitHubról való letöltésével futtathatja és hibakeresést végezhet a dokumentum összes kódjában.

Változáskövető hibakeresési nézet

A változáskövető hibakeresési nézet az IDE hibakeresőjében érhető el. Például a Visual Studióval:

A változáskövetési hibakeresési nézet elérése a Visual Studio hibakeresőből

Közvetlenül a kódból is elérhető, például a hibakeresési nézet elküldéséhez a konzolra:

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

A hibakeresési nézet rövid és hosszú formátumú. A rövid űrlap a nyomon követett entitásokat, azok állapotát és kulcsértékeit jeleníti meg. A hosszú űrlap az összes tulajdonságot és navigációs értéket és állapotot is tartalmazza.

A rövid nézet

Tekintsünk meg egy hibakeresési nézetet a dokumentum végén látható modell használatával. Először is nyomon követünk néhány entitást, és különböző állapotba helyezzük őket, csak azért, hogy jó változáskövetési adatokat tekintsünk meg:

using var context = new BlogsContext();

var blogs = await context.Blogs
    .Include(e => e.Posts).ThenInclude(e => e.Tags)
    .Include(e => e.Assets)
    .ToListAsync();

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

Az itt látható rövid nézet nyomtatása a következő kimenetet eredményezi:

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

Megjegyzés:

  • Minden nyomon követett entitás az elsődleges kulcs (PK) értékével van felsorolva. Például: Blog {Id: 1}.
  • Ha az entitás megosztott típusú entitástípus, akkor a CLR-típus is megjelenik. Például: PostTag (Dictionary<string, object>).
  • Következőnek a EntityState látható. Ez lesz az egyik Unchanged, Added, Modifiedvagy Deleted.
  • Az alternatív kulcsok (AK-k) értékei a következőkben láthatók. Például: AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Végül megjelennek az idegen kulcsok (FK-k) értékei. Például: FK {PostsId: 4} FK {TagsId: 2}.

A hosszú nézet

A hosszú nézet a rövid nézethez hasonlóan küldhető el a konzolra:

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

A fenti rövid nézettel megegyező állapot kimenete a következő:

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

Minden nyomon követett entitás és állapota a korábbiakhoz hasonlóan jelenik meg. A hosszú nézet azonban a tulajdonság- és navigációs értékeket is megjeleníti.

Tulajdonságértékek

Minden tulajdonság esetében a hosszú nézet azt mutatja, hogy a tulajdonság egy elsődleges kulcs (PK), alternatív kulcs (AK) vagy idegen kulcs (FK) része-e. Például:

  • Blog.Id elsődleges kulcstulajdonság: Id: 1 PK
  • Blog.AssetsId egy másik kulcstulajdonság: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId idegen kulcs tulajdonság: BlogId: 2 FK
  • BlogAssets.Id egyszerre elsődleges kulcs és külső kulcs tulajdonság: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

A módosított tulajdonságértékek ilyenként vannak megjelölve, és a tulajdonság eredeti értéke is megjelenik. Például: Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Végül az Added ideiglenes kulcsértékekkel rendelkező entitások azt jelzik, hogy az érték ideiglenes. Például: Id: -2147482643 PK Temporary.

A navigációs értékek azon entitások elsődleges kulcsértékeivel jelennek meg, amelyekre a navigáció hivatkozik. A fenti kimenetben például a 3. bejegyzés a 2. bloghoz kapcsolódik. Ez azt jelenti, hogy a Post.Blog navigáció a Blog példányra mutat azonosítóval 2. Ez a következőképpen jelenik meg Blog: {Id: 2}.

Ugyanez történik a gyűjtemény-navigációk esetében is, kivéve, hogy ebben az esetben több kapcsolódó entitás is lehet. A gyűjteménynavigáció Blog.Posts például három entitást tartalmaz, amelyek 1, 2 és -2147482643 fő értékeket tartalmaznak. Ez a következőképpen jelenik meg [{Id: 1}, {Id: 2}, {Id: -2147482643}].

Változáskövetési naplózás

A változáskövető naplózza az üzeneteket, DebugLogLevel amikor tulajdonság- vagy navigációs változásokat észlel. Ha például ChangeTracker.DetectChanges() a rendszer meghívja a dokumentum tetején található kódban, és engedélyezve van a hibakeresési naplózás, akkor a következő naplók jönnek létre:

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

Az alábbi táblázat összefoglalja a változáskövető naplózási üzeneteit:

Eseményazonosító Leírás
CoreEventId.DetectChangesStarting DetectChanges() indul
CoreEventId.DetectChangesCompleted DetectChanges() befejeződött
CoreEventId.PropertyChangeDetected Módosult egy normál tulajdonságérték
CoreEventId.ForeignKeyChangeDetected Módosult egy idegenkulcs-tulajdonság értéke
CoreEventId.CollectionChangeDetected A teljes gyűjtemény navigációjához kapcsolódó entitások lettek hozzáadva vagy eltávolítva.
CoreEventId.ReferenceChangeDetected A hivatkozásnavigáció egy másik entitásra mutat, vagy null értékre van állítva
CoreEventId.StartedTracking Az EF Core elkezdett nyomon követni egy entitást
CoreEventId.StateChanged Egy EntityState entitás módosult
CoreEventId.ValueGenerated Érték lett létrehozva egy tulajdonsághoz
CoreEventId.SkipCollectionChangeDetected A kihagyott gyűjtemény navigációs sávjához kapcsolódó entitások lettek hozzáadva vagy eltávolítva

A modell

A fenti példákhoz használt modell a következő entitástípusokat tartalmazza:

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
}

A modell többnyire konvenció szerint van konfigurálva, mindössze néhány sor az OnModelCreatingben:

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