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:
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
oDeleted
. - 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
.
Valores de navegación
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 Debug
LogLevel 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);
}