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 em todo o código neste documento baixando o código de exemplo do GitHub.

Alterar depuração do rastreador

O modo de depuração do construtor de modelos 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 o modo de depuração para o console:

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

O modo de depuração tem um formulário curto e longo. 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 e navegação e estado.

O modo de exibição curto

Vamos examinar um exemplo de modo de exibição de depuração usando o modelo mostrado 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 = 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();

Imprimir o modo de exibição curto nesse 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 controlada é 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 próximo EntityState é mostrado a seguir. E ele será um de Unchanged, Added, Modified ou Deleted.
  • Os valores de chaves alternativas (AKs) são mostrados em seguida. 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}.

O modo de exibição longo

O modo de exibição longo pode ser enviado para o console da mesma maneira que o modo de exibição curto:

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

A saída para o mesmo estado que o modo de exibição curto 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 controlada e seu estado são mostrados como antes. 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 FK (chave estrangeira). 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 Debug LogLevel sempre que ele detecta alterações de propriedade ou de navegação. Por exemplo, quando ChangeTracker.DetectChanges() é chamado no código na parte superior 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 Uma navegação de coleção não 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 nula
CoreEventId.StartedTracking O EF Core começou a rastrear uma entidade
CoreEventId.StateChanged A EntityState de uma entidade foi alterada
CoreEventId.ValueGenerated Um valor foi gerado para uma propriedade
CoreEventId.SkipCollectionChangeDetected Uma navegação de coleção de ignorar 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);
}