Compartir a través de


Depuración de seguimiento de cambios

El seguimiento de cambios de Entity Framework Core (EF Core) genera dos tipos de salida para ayudar con la depuración:

  • El ChangeTracker.DebugView proporciona una vista legible de todas las entidades a las que se realiza el seguimiento
  • Los mensajes de registro de nivel de depuración se generan cuando el rastreador de cambios detecta el estado y corrige las relaciones

Sugerencia

En este documento se da por supuesto que se comprenden los estados de entidad y los conceptos básicos del seguimiento de cambios de EF Core. Consulte Herramienta de seguimiento de cambios en EF Core para obtener más información sobre estos temas.

Sugerencia

Puede ejecutar y depurar en todo el código de este documento descargando el código de ejemplo de GitHub.

Vista de depuración del rastreador de cambios

Se puede acceder a la vista de depuración del rastreador de cambios en el depurador del IDE. Por ejemplo, con Visual Studio:

Acceso a la vista de depuración del rastreador de cambios desde el depurador de Visual Studio

También se puede acceder directamente desde el código, por ejemplo, para enviar la vista de depuración a la consola:

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

La vista de depuración tiene un formato corto y uno largo. El formulario corto muestra las entidades con seguimiento, su estado y los valores de clave. El formato largo también incluye todos los valores de propiedad y navegación, además del estado.

Vista corta

Echemos un vistazo a un ejemplo de vista de depuración mediante el modelo que se muestra al final de este documento. En primer lugar, realizaremos un seguimiento de algunas entidades y las colocaremos en algunos estados diferentes, por lo que tendremos buenos datos de seguimiento de cambios para ver:

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

La impresión de la vista corta en este punto, tal y como se muestra anteriormente, da como resultado la siguiente salida:

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 entidad con seguimiento aparece con su valor de clave principal (PK). Por ejemplo, Blog {Id: 1}.
  • Si la entidad fuera un tipo de entidad de tipo compartido, también se mostrará el tipo CLR. Por ejemplo, PostTag (Dictionary<string, object>).
  • A continuación, se muestra el EntityState. Este será uno de Unchanged, Added, Modified o Deleted.
  • A continuación, se muestran los valores de las claves alternativas (AK). Por ejemplo, AK {AssetsId: ed727978-1ffe-4709-baee-73913e8e44a0}.
  • Por último, se muestran los valores de las claves externas (FK). Por ejemplo, FK {PostsId: 4} FK {TagsId: 2}.

Vista larga

La vista larga se puede enviar a la consola de la misma manera que la vista corta:

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

La salida del mismo estado que la vista corta anterior es:

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 entidad con seguimiento y su estado se muestran como antes. Sin embargo, la vista larga también muestra los valores de propiedad y navegación.

Valores de propiedad

Para cada propiedad, la vista larga muestra si la propiedad forma parte de una clave principal (PK), una clave alternativa (AK) o una clave externa (FK). Por ejemplo:

  • Blog.Id es una propiedad de clave principal: Id: 1 PK
  • Blog.AssetsId es una propiedad de clave alternativa: AssetsId: 'ed727978-1ffe-4709-baee-73913e8e44a0' AK
  • Post.BlogId es una propiedad de clave externa: BlogId: 2 FK
  • BlogAssets.Id es tanto una propiedad de clave externa como de clave principal: Id: '3a54b880-2b9d-486b-9403-dc2e52d36d65' PK FK

Los valores de propiedad modificados se marcan como tales y, también, se muestra el valor original de la propiedad. Por ejemplo, Name: '.NET Blog (All new!)' Modified Originally '.NET Blog'.

Por último, Added entidades con valores de clave temporal indican que el valor es temporal. Por ejemplo, Id: -2147482643 PK Temporary.

Los valores de navegación se muestran mediante los valores de clave principal de las entidades a las que hace referencia la navegación. Por ejemplo, en la salida anterior, la entrada 3 está relacionada con el blog 2. Esto significa que la navegación Post.Blog apunta a la instancia Blog con el identificador 2. Esto se muestra como Blog: {Id: 2}.

Lo mismo sucede con las navegaciones de colección, salvo que en este caso puede haber varias entidades relacionadas. Por ejemplo, la navegación de colección Blog.Posts contiene tres entidades, con los valores clave 1, 2 y -2147482643, respectivamente. Esto se muestra como [{Id: 1}, {Id: 2}, {Id: -2147482643}].

Registro de seguimiento de cambios

El rastreador de cambios registra los mensajes en el DebugLogLevel cada vez que detecta cambios de propiedad o navegación. Por ejemplo, cuando se llame a ChangeTracker.DetectChanges() en el código de la parte superior de este documento y el registro de depuración esté habilitado, se generarán los registros siguientes:

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

En la tabla siguiente, se resumen los mensajes de registro de seguimiento de cambios:

Id. de evento Descripción
CoreEventId.DetectChangesStarting DetectChanges() se está iniciando
CoreEventId.DetectChangesCompleted DetectChanges() se ha completado
CoreEventId.PropertyChangeDetected Un valor de propiedad normal ha cambiado
CoreEventId.ForeignKeyChangeDetected Un valor de propiedad de clave externa ha cambiado
CoreEventId.CollectionChangeDetected Una navegación de recopilación que no se omite ha tenido entidades relacionadas agregadas o eliminadas.
CoreEventId.ReferenceChangeDetected Se ha cambiado una navegación de referencia para que apunte a otra entidad o se establezca en NULL
CoreEventId.StartedTracking EF Core inició el seguimiento de una entidad
CoreEventId.StateChanged El EntityState de una entidad ha cambiado
CoreEventId.ValueGenerated Se generó un valor para una propiedad
CoreEventId.SkipCollectionChangeDetected Una navegación de recopilación de omisión ha tenido entidades relacionadas agregadas o eliminadas

El modelo de

El modelo usado para los ejemplos anteriores contiene los siguientes tipos de entidad:

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
}

El modelo se configura principalmente por convención, con solo unas pocas líneas en 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);
}