Acceso a entidades sometidas a seguimiento

Existen cuatro API principales para acceder a las entidades de las que se hace un seguimiento con un DbContext:

Cada uno de estos cambios se describe con más detalle en las siguientes secciones.

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 Change Tracking en EF Core para 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.

Uso de instancias de DbContext.Entry y EntityEntry

Para cada entidad con seguimiento, Entity Framework Core (EF Core) realiza un seguimiento de:

  • Estado general de la entidad. Se trata de una de las opciones Unchanged, Modified, Added o Deleted; consulte Change Tracking en EF Core para más información.
  • Las relaciones entre entidades sometidas a seguimiento. Por ejemplo, el blog al que pertenece una entrada.
  • Los "valores actuales" de las propiedades.
  • Los "valores originales" de las propiedades, cuando esta información está disponible. Los valores originales son los valores de propiedad que existían cuando se consultaba la entidad desde la base de datos.
  • Qué valores de propiedad se han modificado desde que se consultaron.
  • Otra información sobre los valores de propiedad, como si el valor es temporal o no.

Pasar una instancia de entidad a DbContext.Entry da como resultado un EntityEntry<TEntity> que proporciona acceso a esta información para la entidad dada. Por ejemplo:

using var context = new BlogsContext();

var blog = context.Blogs.Single(e => e.Id == 1);
var entityEntry = context.Entry(blog);

En las secciones siguientes se muestra cómo usar EntityEntry para acceder y manipular el estado de entidad, así como el estado de las propiedades y las navegaciones de la entidad.

Trabajo con la entidad

El uso más común de EntityEntry<TEntity> es acceder al EntityState actual de una entidad. Por ejemplo:

var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
    context.Entry(blog).State = EntityState.Modified;
}

El método Entry también se puede usar en entidades que aún no se han seguido. Esto no inicia el seguimiento de la entidad; el estado de la entidad sigue siendo Detached. Sin embargo, se puede usar el valor de EntityEntry devuelto para cambiar el estado de la entidad, momento en el que se realizará el seguimiento de la entidad en el estado especificado. Por ejemplo, el código siguiente iniciará el seguimiento de una instancia de Blog como Added:

var newBlog = new Blog();
Debug.Assert(context.Entry(newBlog).State == EntityState.Detached);

context.Entry(newBlog).State = EntityState.Added;
Debug.Assert(context.Entry(newBlog).State == EntityState.Added);

Sugerencia

Al contrario que en EF6, la configuración del estado de una entidad individual no provocará el seguimiento de todas las entidades conectadas. Esto hace que establecer el estado de esta forma sea una operación de menor nivel que llamar a Add, Attach o Update, que operan sobre un grafo completo de entidades.

En la tabla siguiente se resumen las formas de usar EntityEntry para trabajar con una entidad completa:

Miembro EntityEntry Descripción
EntityEntry.State Obtiene y establece el EntityState de la entidad.
EntityEntry.Entity Obtiene la instancia de entidad.
EntityEntry.Context El DbContext que realiza el seguimiento de esta entidad.
EntityEntry.Metadata Metadatos de IEntityType para el tipo de entidad.
EntityEntry.IsKeySet Si la entidad tiene o no su valor de clave establecido.
EntityEntry.Reload() Sobrescribe los valores de propiedad con valores leídos de la base de datos.
EntityEntry.DetectChanges() Fuerza la detección de cambios solo para esta entidad; consulte Detección de cambios y notificaciones.

Trabajo con una sola propiedad

Varias sobrecargas de EntityEntry<TEntity>.Property permiten el acceso a información sobre una propiedad individual de una entidad. Por ejemplo, usar una API fuertemente tipada y fluida:

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);

En su lugar, el nombre de la propiedad se puede pasar como una cadena. Por ejemplo:

PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");

A continuación, se puede usar el PropertyEntry<TEntity,TProperty> devuelto para obtener acceso a la información sobre la propiedad. Por ejemplo, se puede usar para obtener y establecer el valor actual de la propiedad en esta entidad:

string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";

Ambos métodos Property usados anteriormente devuelven una instancia genérica de PropertyEntry<TEntity,TProperty> fuertemente tipada. Se prefiere usar este tipo genérico porque permite el acceso a valores de propiedad sin tipos de valor de conversión boxing. Sin embargo, si el tipo de entidad o propiedad no se conoce en tiempo de compilación, se puede obtener un PropertyEntry no genérico en su lugar:

PropertyEntry propertyEntry = context.Entry(blog).Property("Name");

Esto permite el acceso a la información de propiedad de cualquier propiedad independientemente de su tipo, a costa de los tipos de valor de conversión boxing. Por ejemplo:

object blog = context.Blogs.Single(e => e.Id == 1);

object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";

En la tabla siguiente se resume la información de propiedad expuesta por PropertyEntry:

Miembro PropertyEntry Descripción
PropertyEntry<TEntity,TProperty>.CurrentValue Obtiene y establece el valor actual de la propiedad.
PropertyEntry<TEntity,TProperty>.OriginalValue Obtiene y establece el valor original de la propiedad, si está disponible.
PropertyEntry<TEntity,TProperty>.EntityEntry Referencia inversa a EntityEntry<TEntity> para la entidad.
PropertyEntry.Metadata Los metadatos de IProperty de la propiedad.
PropertyEntry.IsModified Indica si esta propiedad está marcada como modificada y permite cambiar este estado.
PropertyEntry.IsTemporary Indica si esta propiedad está marcada como temporal y permite cambiar este estado.

Notas:

  • El valor original de una propiedad es el valor que tenía la propiedad cuando se consultó la entidad desde la base de datos. Sin embargo, los valores originales no están disponibles si la entidad se desconectó y, a continuación, se adjuntó explícitamente a otro DbContext, por ejemplo con Attach o Update. En este caso, el valor original devuelto será el mismo que el valor actual.
  • SaveChanges solo actualizará las propiedades marcadas como modificadas. Establezca IsModified en true para forzar a EF Core a actualizar un valor de propiedad determinado o establecerlo en false para evitar que EF Core actualice el valor de la propiedad.
  • Los valores temporales suelen ser generados por los generadores de valores de EF Core. Al establecer el valor actual de una propiedad, se reemplazará el valor temporal por el valor especificado y se marcará la propiedad como no temporal. Establezca IsTemporary en true para forzar que un valor sea temporal incluso después de que se haya establecido explícitamente.

Trabajo con una sola navegación

Varias sobrecargas de EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collection y EntityEntry.Navigation permiten el acceso a información sobre una navegación individual.

Se accede a las navegaciones de referencia a una sola entidad relacionada a través de los métodos Reference. Las navegaciones de referencia apuntan a los lados "uno" de las relaciones uno a varios y a ambos lados de las relaciones uno a uno. Por ejemplo:

ReferenceEntry<Post, Blog> referenceEntry1 = context.Entry(post).Reference(e => e.Blog);
ReferenceEntry<Post, Blog> referenceEntry2 = context.Entry(post).Reference<Blog>("Blog");
ReferenceEntry referenceEntry3 = context.Entry(post).Reference("Blog");

Las navegaciones también pueden ser colecciones de entidades relacionadas cuando se usan para los lados "varios" de relaciones de uno a varios y varios a varios. Los métodos Collection se usan para acceder a las navegaciones de recopilación. Por ejemplo:

CollectionEntry<Blog, Post> collectionEntry1 = context.Entry(blog).Collection(e => e.Posts);
CollectionEntry<Blog, Post> collectionEntry2 = context.Entry(blog).Collection<Post>("Posts");
CollectionEntry collectionEntry3 = context.Entry(blog).Collection("Posts");

Algunas operaciones son comunes para todas las navegaciones. Se puede tener acceso a ellos para las navegaciones de referencia y colección mediante el método EntityEntry.Navigation. Tenga en cuenta que solo hay acceso no genérico disponible al acceder a todas las navegaciones juntas. Por ejemplo:

NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");

En la tabla siguiente se resumen las formas de usar ReferenceEntry<TEntity,TProperty>, CollectionEntry<TEntity,TRelatedEntity> y NavigationEntry:

Miembro NavigationEntry Descripción
MemberEntry.CurrentValue Obtiene y establece el valor actual de la navegación. Esta es toda la colección para las navegaciones de recopilación.
NavigationEntry.Metadata Metadatos de INavigationBase para la navegación.
NavigationEntry.IsLoaded Obtiene o establece un valor que indica si la entidad o colección relacionada se ha cargado completamente desde la base de datos.
NavigationEntry.Load() Carga la entidad o colección relacionada de la base de datos; consulte Carga explícita de datos relacionados.
NavigationEntry.Query() La consulta que EF Core usaría para cargar esta navegación como un IQueryable que puede componerse posteriormente; consulte Carga explícita de datos relacionados.

Trabajo con todas las propiedades de una entidad

EntityEntry.Properties devuelve un IEnumerable<T> de PropertyEntry para cada propiedad de la entidad. Esto se puede usar para realizar una acción para cada propiedad de la entidad. Por ejemplo, para establecer cualquier propiedad DateTime en DateTime.Now:

foreach (var propertyEntry in context.Entry(blog).Properties)
{
    if (propertyEntry.Metadata.ClrType == typeof(DateTime))
    {
        propertyEntry.CurrentValue = DateTime.Now;
    }
}

Además, EntityEntry contiene varios métodos para obtener y establecer todos los valores de propiedad al mismo tiempo. Estos métodos usan la clase PropertyValues, que representa una colección de propiedades y sus valores. PropertyValues se puede obtener para los valores actuales u originales, o para los valores almacenados actualmente en la base de datos. Por ejemplo:

var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = context.Entry(blog).GetDatabaseValues();

Estos objetos PropertyValues no son muy útiles por sí mismos. Sin embargo, se pueden combinar para realizar operaciones comunes necesarias al manipular entidades. Esto resulta útil cuando se trabaja con objetos de transferencia de datos y al resolver conflictos de simultaneidad optimista. En las secciones siguientes se muestran algunos ejemplos.

Establecimiento de valores actuales u originales de una entidad o DTO

Los valores actuales u originales de una entidad se pueden actualizar copiando valores de otro objeto. Por ejemplo, considere un objeto de transferencia de datos (DTO) BlogDto con las mismas propiedades que el tipo de entidad:

public class BlogDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Esto se puede usar para establecer los valores actuales de una entidad sometida a seguimiento mediante PropertyValues.SetValues:

var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDto);

Esta técnica se usa a veces al actualizar una entidad con valores obtenidos de una llamada de servicio o un cliente en una aplicación de n niveles. Tenga en cuenta que el objeto usado no tiene que ser del mismo tipo que la entidad, siempre y cuando tenga propiedades cuyos nombres coincidan con los de la entidad. En el ejemplo anterior, se usa una instancia de DTO BlogDto para establecer los valores actuales de una entidad Blog sometida a seguimiento.

Tenga en cuenta que las propiedades solo se marcarán como modificadas si el conjunto de valores difiere del valor actual.

Establecimiento de valores actuales u originales de un diccionario

En el ejemplo anterior se establecen valores de una entidad o una instancia de DTO. El mismo comportamiento está disponible cuando los valores de propiedad se almacenan como pares nombre-valor en un diccionario. Por ejemplo:

var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };

context.Entry(blog).CurrentValues.SetValues(blogDictionary);

Establecimiento de valores actuales u originales de la base de datos

Los valores actuales u originales de una entidad se pueden actualizar con los valores más recientes de la base de datos llamando a GetDatabaseValues() o GetDatabaseValuesAsync usando el objeto devuelto para establecer valores actuales u originales, o ambos. Por ejemplo:

var databaseValues = context.Entry(blog).GetDatabaseValues();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);

Creación de un objeto clonado que contenga valores actuales, originales o de base de datos

El objeto PropertyValues devuelto por CurrentValues, OriginalValues o GetDatabaseValues se puede usar para crear un clon de la entidad mediante PropertyValues.ToObject(). Por ejemplo:

var clonedBlog = context.Entry(blog).GetDatabaseValues().ToObject();

Tenga en cuenta que ToObject devuelve una nueva instancia que no realiza el seguimiento de DbContext. El objeto devuelto tampoco tiene relaciones establecidas en otras entidades.

El objeto clonado puede ser útil para resolver problemas relacionados con las actualizaciones simultáneas de la base de datos, especialmente cuando se enlazan datos a objetos de un tipo determinado. Consulte simultaneidad optimista para más información.

Trabajo con todas las navegaciones de una entidad

EntityEntry.Navigations devuelve un IEnumerable<T> de NavigationEntry para cada navegación de la entidad. EntityEntry.References y EntityEntry.Collections hacen lo mismo, pero restringidos a las navegaciones de referencia o colección, respectivamente. Esto se puede usar para realizar una acción para cada navegación de la entidad. Por ejemplo, para forzar la carga de todas las entidades relacionadas:

foreach (var navigationEntry in context.Entry(blog).Navigations)
{
    navigationEntry.Load();
}

Trabajo con todos los miembros de una entidad

Las propiedades normales y las propiedades de navegación tienen un estado y un comportamiento diferentes. Por lo tanto, es habitual procesar las navegaciones y las no navegaciones por separado, como se muestra en las secciones anteriores. Sin embargo, a veces puede ser útil hacer algo con cualquier miembro de la entidad, independientemente de si es una navegación o propiedad normal. EntityEntry.Member y EntityEntry.Members se proporcionan para este fin. Por ejemplo:

foreach (var memberEntry in context.Entry(blog).Members)
{
    Console.WriteLine(
        $"Member {memberEntry.Metadata.Name} is of type {memberEntry.Metadata.ClrType.ShortDisplayName()} and has value {memberEntry.CurrentValue}");
}

La ejecución de este código en un blog del ejemplo genera la siguiente salida:

Member Id is of type int and has value 1
Member Name is of type string and has value .NET Blog
Member Posts is of type IList<Post> and has value System.Collections.Generic.List`1[Post]

Sugerencia

La vista de depuración del rastreador de cambios muestra información como esta. La vista de depuración de todo el seguimiento de cambios se genera a partir de la EntityEntry.DebugView individual de cada entidad sometida a seguimiento.

Find y FindAsync

DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Find y DbSet<TEntity>.FindAsync están diseñados para una búsqueda eficaz de una sola entidad cuando se conoce su clave principal. Find comprueba primero si la entidad ya está sometida a seguimiento y, en caso afirmativo, devuelve la entidad inmediatamente. Solo se realiza una consulta a la base de datos si no se realiza un seguimiento local de la entidad. Por ejemplo, considere este código que llama a Find dos veces para la misma entidad:

using var context = new BlogsContext();

Console.WriteLine("First call to Find...");
var blog1 = context.Blogs.Find(1);

Console.WriteLine($"...found blog {blog1.Name}");

Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = context.Blogs.Find(1);
Debug.Assert(blog1 == blog2);

Console.WriteLine("...returned the same instance without executing a query.");

La salida de este código (incluido el registro de EF Core) al usar SQLite es:

First call to Find...
info: 12/29/2020 07:45:53.682 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[@__p_0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
      SELECT "b"."Id", "b"."Name"
      FROM "Blogs" AS "b"
      WHERE "b"."Id" = @__p_0
      LIMIT 1
...found blog .NET Blog

Second call to Find...
...returned the same instance without executing a query.

Observe que la primera llamada no encuentra la entidad localmente y, por tanto, ejecuta una consulta de base de datos. Por el contrario, la segunda llamada devuelve la misma instancia sin consultar la base de datos porque ya se está realizando el seguimiento.

Find devuelve null si no se realiza un seguimiento local de una entidad con la clave especificada y no existe en la base de datos.

Claves compuestas

La búsqueda también se puede usar con claves compuestas. Por ejemplo, considere una entidad de OrderLine con una clave compuesta formada por el id. del pedido y el id. del producto:

public class OrderLine
{
    public int OrderId { get; set; }
    public int ProductId { get; set; }

    //...
}

La clave compuesta debe configurarse en DbContext.OnModelCreating para definir las partes clave y su orden. Por ejemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<OrderLine>()
        .HasKey(e => new { e.OrderId, e.ProductId });
}

Observe que OrderId es la primera parte de la clave y ProductId es la segunda parte de la clave. Este orden se debe usar al pasar valores de clave a Find. Por ejemplo:

var orderline = context.OrderLines.Find(orderId, productId);

Uso de ChangeTracker.Entries para acceder a todas las entidades con seguimiento

Hasta ahora solo hemos accedido a un EntityEntry cada vez. ChangeTracker.Entries() devuelve una EntityEntry para cada entidad de la que DbContext realiza un seguimiento en ese momento. Por ejemplo:

using var context = new BlogsContext();
var blogs = context.Blogs.Include(e => e.Posts).ToList();

foreach (var entityEntry in context.ChangeTracker.Entries())
{
    Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}

Este código genera el siguiente resultado:

Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2

Tenga en cuenta que se devuelven entradas para blogs y publicaciones. En su lugar, los resultados se pueden filtrar a un tipo de entidad específico mediante la sobrecarga genérica de ChangeTracker.Entries<TEntity>():

foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

La salida de este código muestra que solo se devuelven entradas:

Found Post entity with ID 1
Found Post entity with ID 2

Además, el uso de la sobrecarga genérica devuelve instancias genéricas de EntityEntry<TEntity>. Esto es lo que permite el acceso de tipo fluido a la propiedad Id en este ejemplo.

El tipo genérico usado para el filtrado no tiene que ser un tipo de entidad asignado; en su lugar, se puede usar un tipo base o una interfaz no asignados. Por ejemplo, si todos los tipos de entidad del modelo implementan una interfaz que define su propiedad de clave:

public interface IEntityWithKey
{
    int Id { get; set; }
}

A continuación, esta interfaz se puede usar para trabajar con la clave de cualquier entidad sometida a seguimiento de forma fuertemente tipada. Por ejemplo:

foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
    Console.WriteLine(
        $"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}

Uso de DbSet.Local para consultar entidades sometidas a seguimiento

Las consultas de EF Core siempre se ejecutan en la base de datos y solo devuelven entidades que se han guardado en la base de datos. DbSet<TEntity>.Local proporciona un mecanismo para consultar DbContext para las entidades locales y sometidas a seguimiento.

Puesto que DbSet.Local se usa para consultar entidades sometidas a seguimiento, es habitual cargar entidades en DbContext y, a continuación, trabajar con esas entidades cargadas. Esto es especialmente cierto para el enlace de datos, pero también puede ser útil en otras situaciones. Por ejemplo, en el código siguiente, la base de datos se consulta primero para todos los blogs y entradas. El método de extensión Load se usa para ejecutar esta consulta con los resultados de los que realiza el seguimiento el contexto sin devolverse directamente a la aplicación. (El uso de ToList o similar tiene el mismo efecto, pero con la sobrecarga de crear la lista devuelta, que no es necesaria aquí). A continuación, en el ejemplo se usa DbSet.Local para acceder a las entidades de seguimiento local:

using var context = new BlogsContext();

context.Blogs.Include(e => e.Posts).Load();

foreach (var blog in context.Blogs.Local)
{
    Console.WriteLine($"Blog: {blog.Name}");
}

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"Post: {post.Title}");
}

Observe que, a diferencia de ChangeTracker.Entries(), DbSet.Local devuelve instancias de entidad directamente. EntityEntry siempre se puede obtener para la entidad devuelta llamando a DbContext.Entry.

La vista local

DbSet<TEntity>.Local devuelve una vista de las entidades sometidas a seguimiento local que refleja el EntityState actual de esas entidades. En concreto, esto significa que:

  • Se incluyen Added entidades. Tenga en cuenta que este no es el caso de las consultas normales de EF Core, ya que Added entidades aún no existen en la base de datos y, por lo tanto, nunca son devueltas por una consulta a la base de datos.
  • Se excluyen Deleted entidades. Observe que, de nuevo, este no es el caso de las consultas normales de EF Core, ya que Deleted entidades siguen existiendo en la base de datos y, por tanto, son devueltas por las consultas de la base de datos.

Todo esto significa que DbSet.Local es la vista sobre los datos que refleja el estado conceptual actual del grafo de entidades, con Added entidades incluidas y Deleted entidades excluidas. Esto coincide con lo que se espera que sea el estado de la base de datos después de llamar a SaveChanges.

Normalmente es la vista ideal para el enlace de datos, ya que presenta al usuario los datos a medida que lo entienden en función de los cambios realizados por la aplicación.

El código siguiente muestra esto marcando una publicación como Deleted y, a continuación, agregando una nueva publicación, marcándola como Added:

using var context = new BlogsContext();

var posts = context.Posts.Include(e => e.Blog).ToList();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Remove(posts[1]);

context.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

La salida de este código es:

Local view after loading posts:
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing F# 5
  Post: Announcing .NET 5.0
Local view after adding and deleting posts:
  Post: What’s next for System.Text.Json?
  Post: Announcing the Release of EF Core 5.0
  Post: Announcing .NET 5.0

Observe que la publicación eliminada se quita de la vista local y se incluye la publicación agregada.

Uso de Local para agregar y quitar entidades

DbSet<TEntity>.Local devuelve una instancia de LocalView<TEntity>. Se trata de una implementación de ICollection<T> que genera y responde a las notificaciones cuando se agregan y quitan entidades de la colección. (Este es el mismo concepto que ObservableCollection<T>, pero se implementa como una proyección sobre las entradas existentes de seguimiento de cambios de EF Core, en lugar de como una colección independiente).

Las notificaciones de la vista local se enlazan al seguimiento de cambios de DbContext para que la vista local permanezca sincronizada con DbContext. Concretamente:

  • Añadir una nueva entidad a DbSet.Local hace que sea sometida a seguimiento por parte de DbContext, normalmente en el estado Added. (Si la entidad ya tiene un valor de clave generado, se realiza el seguimiento como Unchanged en su lugar).
  • Quitar una entidad de DbSet.Local hace que se marque como Deleted.
  • Una entidad que pase a estar sometida a seguimiento por DbContext aparecerá automáticamente en la colección de DbSet.Local. Por ejemplo, ejecutar una consulta para incorporar más entidades automáticamente hace que se actualice la vista local.
  • Una entidad marcada como Deleted se quitará de la colección local automáticamente.

Esto significa que la vista local se puede usar para manipular entidades de seguimiento simplemente agregando y quitando de la colección. Por ejemplo, vamos a modificar el código de ejemplo anterior para agregar y quitar publicaciones de la colección local:

using var context = new BlogsContext();

var posts = context.Posts.Include(e => e.Blog).ToList();

Console.WriteLine("Local view after loading posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

context.Posts.Local.Remove(posts[1]);

context.Posts.Local.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?",
        Content = ".NET 5.0 was released recently and has come with many...",
        Blog = posts[0].Blog
    });

Console.WriteLine("Local view after adding and deleting posts:");

foreach (var post in context.Posts.Local)
{
    Console.WriteLine($"  Post: {post.Title}");
}

La salida permanece sin cambios en el ejemplo anterior porque los cambios realizados en la vista local se sincronizan con DbContext.

Uso de la vista local para el enlace de datos de Windows Forms o WPF

DbSet<TEntity>.Local constituye la base para el enlace de datos a entidades de EF Core. Sin embargo, tanto Windows Forms como WPF funcionan mejor cuando se usan con el tipo específico de colección de notificaciones que esperan. La vista local admite la creación de estos tipos de colección específicos:

Por ejemplo:

ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();

Consulte Introducción a WPF para más información sobre el enlace de datos de WPF con EF Core e Introducción a Windows Forms para obtener más información sobre el enlace de datos de Windows Forms con EF Core.

Sugerencia

La vista local de una instancia de DbSet determinada se crea de forma diferida cuando se accede por primera vez y, a continuación, se almacena en caché. La creación de LocalView es rápida y no usa memoria significativa. Sin embargo, llama a DetectChanges, que puede ser lento para un gran número de entidades. Las colecciones creadas por ToObservableCollection y ToBindingList también se crean de forma diferida y, a continuación, se almacenan en caché. Ambos métodos crean nuevas colecciones, lo que puede resultar lento y usar mucha memoria cuando se trata de miles de entidades.