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:
- DbContext.Entry devuelve una instancia de EntityEntry<TEntity> para una instancia de entidad dada.
- ChangeTracker.Entries devuelve instancias de EntityEntry<TEntity> para todas las entidades sometidas a seguimiento, o para todas las entidades sometidas a seguimiento de un tipo determinado.
- DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Find y DbSet<TEntity>.FindAsync encuentran una única entidad por clave principal, buscando primero en las entidades sometidas a seguimiento y después consultando la base de datos si es necesario.
- DbSet<TEntity>.Local devuelve entidades reales (no instancias de EntityEntry) para las entidades del tipo de entidad representado por DbSet.
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
oDeleted
; 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
oUpdate
. 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 queAdded
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 queDeleted
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 estadoAdded
. (Si la entidad ya tiene un valor de clave generado, se realiza el seguimiento comoUnchanged
en su lugar). - Quitar una entidad de
DbSet.Local
hace que se marque comoDeleted
. - 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:
- LocalView<TEntity>.ToObservableCollection() devuelve un ObservableCollection<T> para el enlace de datos de WPF.
- LocalView<TEntity>.ToBindingList() devuelve un BindingList<T> para el enlace de datos de Windows Forms.
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.