Compartilhar via


Alterar Depuração do Rastreador

O rastreador 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 exibição legível por humanos de todas as entidades que estão sendo rastreadas
  • Mensagens de log no nível de depuração são geradas quando o rastreador de alterações detecta o estado e corrige relações

Dica

Esse documento pressupõe que os estados de entidade e as noções básicas 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.

Dica

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

Alterar depuração do rastreador

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

Acessar o modo de exibição de depuração do rastreador de alterações 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 possui uma versão curta e uma versão longa. O formulário curto mostra entidades controladas, seu estado e valores de chave. O formulário longo também inclui todos os valores de propriedade, navegação e estado.

A visão breve

Vamos examinar um exemplo de visão de depuração utilizando o modelo apresentado no final deste documento. Primeiro, acompanharemos algumas entidades e as colocaremos em alguns estados diferentes, apenas para que tenhamos bons dados de controle de alterações para exibir:

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

A impressão da visão curta neste ponto, conforme 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 monitorada é 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. E ele será um de Unchanged, Added, Modified ou Deleted.
  • Os valores para quaisquer chaves alternativas (AKs) são exibidos a seguir. Por exemplo, AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Por fim, os valores de quaisquer FKs (chaves estrangeiras) são mostrados. Por exemplo, FK {PostsId: 4} FK {TagsId: 2}.

A visão de longo prazo

A visualização longa pode ser enviada ao console da mesma maneira que a visualização curta.

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

A saída para o mesmo estado mostrado na 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 monitorada e seu estado são mostrados como anteriormente. No entanto, o modo de exibição longo também mostra valores de propriedade e navegação.

Valores de propriedade

Para cada propriedade, o modo de exibição longo mostra se a propriedade faz parte ou não 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'.

Por fim, entidades Added com valores de chave temporária 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 que as navegações fazem referência. Por exemplo, na saída acima, a postagem 3 está relacionada ao blog 2. Isso significa que a navegação Post.Blog aponta para a instância Blog com a ID 2. Isso é mostrado como Blog: {Id: 2}.

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

Alterar o registro em log do rastreador

O rastreador de alterações registra mensagens no DebugLogLevel sempre que ele detecta alterações de propriedade ou de navegação. Por exemplo, quando ChangeTracker.DetectChanges() é chamado no trecho de código no início deste documento e o log de depuração é habilitado, os seguintes logs 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 registro em log do rastreador de alterações:

ID do evento Descrição
CoreEventId.DetectChangesStarting DetectChanges() está iniciando
CoreEventId.DetectChangesCompleted DetectChanges() foi concluído
CoreEventId.PropertyChangeDetected Um valor de propriedade normal foi alterado
CoreEventId.ForeignKeyChangeDetected Um valor de propriedade de chave estrangeira foi alterado
CoreEventId.CollectionChangeDetected A navegação de coleção sem pular teve entidades relacionadas adicionadas ou removidas.
CoreEventId.ReferenceChangeDetected Uma navegação de referência foi alterada para apontar para outra entidade ou definida como nula
CoreEventId.StartedTracking O EF Core começou a rastrear uma entidade
CoreEventId.StateChanged A EntityState da entidade foi alterada
CoreEventId.ValueGenerated Um valor foi gerado para uma propriedade
CoreEventId.SkipCollectionChangeDetected Navegação em coleção 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);
}