Partilhar via


Depuração do Rastreador de Mudanças

O controlador de alterações do Entity Framework Core (EF Core) gera dois tipos de saída para ajudar na depuração:

  • O ChangeTracker.DebugView fornece uma visão legível por humanos de todas as entidades que estão sendo rastreadas
  • As mensagens de log ao nível de depuração são geradas quando o controlador de alterações deteta o estado e corrige as relações.

Sugestão

Este documento pressupõe que os estados da entidade e os conceitos básicos do controle de alterações do EF Core sejam compreendidos. Consulte Controle de alterações no EF Core para obter mais informações sobre esses tópicos.

Sugestão

Você pode executar e depurar todo o código neste documento baixando o código de exemplo do GitHub.

Alterar a visualização de depuração do rastreador

A visualização de depuração do controlador de alterações pode ser acessada no depurador do seu IDE. Por exemplo, com o Visual Studio:

Aceder à vista de depuração do rastreio de alterações a partir do depurador do Visual Studio

Ele também pode ser acessado diretamente do código, por exemplo, para enviar a visualização de depuração para o console:

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

A visualização de depuração tem um formulário curto e um formulário longo. O formulário curto mostra entidades controladas, seu estado e valores-chave. O formulário longo também inclui todos os valores e estados de propriedade e navegação.

A vista curta

Vejamos um exemplo de visão de depuração usando o modelo mostrado no final deste documento. Primeiro, vamos rastrear algumas entidades e colocá-las em alguns estados diferentes, apenas para que tenhamos bons dados de controle de alterações para visualizar:

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

Imprimir a visualização curta neste ponto, como mostrado acima, resulta na seguinte saída:

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

Aviso:

  • Cada entidade rastreada é listada com seu valor de chave primária (PK). Por exemplo, Blog {Id: 1}.
  • Se a entidade for um tipo de entidade de tipo compartilhado, seu tipo CLR também será mostrado. Por exemplo, PostTag (Dictionary<string, object>).
  • O EntityState é mostrado a seguir. Este será um dos Unchanged, Added, Modified, ou Deleted.
  • Os valores de quaisquer chaves alternativas (AKs) são mostrados a seguir. Por exemplo, AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Finalmente, os valores para quaisquer chaves estrangeiras (FKs) são exibidos. Por exemplo, FK {PostsId: 4} FK {TagsId: 2}.

A visão de longo prazo

A vista longa pode ser enviada para a consola da mesma forma que a vista curta:

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

A saída para o mesmo estado que a visualização curta acima é:

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

Cada entidade rastreada e seu estado são mostrados como antes. No entanto, a visão ampla também exibe valores de propriedade e navegação.

Valores de propriedade

Para cada propriedade, a visualização longa mostra se a propriedade faz ou não parte de uma chave primária (PK), chave alternativa (AK) ou chave estrangeira (FK). Por exemplo:

  • Blog.Id é uma propriedade de chave primária: Id: 1 PK
  • Blog.AssetsId é uma propriedade de chave alternativa: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId é uma propriedade de chave estrangeira: BlogId: 2 FK
  • BlogAssets.Id é uma chave primária e uma propriedade de chave estrangeira: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

Os valores de propriedade que foram modificados são marcados como tal, e o valor original da propriedade também é mostrado. Por exemplo, Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Finalmente, Added entidades com valores de chave temporários indicam que o valor é temporário. Por exemplo, Id: -2147482643 PK Temporary.

Os valores de navegação são exibidos usando os valores de chave primária das entidades às quais as navegações fazem referência. Por exemplo, na saída acima, o post 3 está relacionado ao blog 2. Isso significa que a Post.Blog navegação aponta para a Blog instância com ID 2. Isso é mostrado como Blog: {Id: 2}.

A mesma coisa acontece para navegações de coleção, exceto que, neste caso, pode haver várias entidades relacionadas. Por exemplo, a navegação Blog.Posts da coleção contém três entidades, com valores de chave 1, 2 e -2147482643 respectivamente. Isso é mostrado como [{Id: 1}, {Id: 2}, {Id: -2147482643}].

Registo do controlador de alterações

O rastreio de alterações regista mensagens no/a DebugLogLevel sempre que deteta alterações de propriedade ou navegação. Por exemplo, quando ChangeTracker.DetectChanges() é chamado no código na parte superior deste documento e os registos de depuração estão ativados, os seguintes registos são gerados:

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

A tabela a seguir resume as mensagens de log do controlador de alterações:

ID do Evento Descrição
CoreEventId.DetectChangesStarting DetectChanges() está a começar
CoreEventId.DetectChangesCompleted DetectChanges() concluiu
CoreEventId.PropertyChangeDetected Um valor de propriedade normal foi alterado
CoreEventId.ForeignKeyChangeDetected O valor de uma propriedade de chave estrangeira foi alterado
CoreEventId.CollectionChangeDetected Uma navegação de coleção que não pode ser ignorada teve entidades relacionadas adicionadas ou removidas.
CoreEventId.ReferenceChangeDetected Uma navegação de referência foi alterada para apontar para outra entidade ou definida como null
CoreEventId.StartedTracking O EF Core começou a rastrear uma entidade
CoreEventId.StateChanged O EntityState de uma entidade mudou
CoreEventId.ValueGenerated Foi gerado um valor para uma propriedade
CoreEventId.SkipCollectionChangeDetected Uma navegação de coleção skip teve entidades relacionadas adicionadas ou removidas

O modelo

O modelo usado para os exemplos acima contém os seguintes tipos de entidade:

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
}

O modelo é configurado principalmente por convenção, com apenas algumas linhas em 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);
}