Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Há quatro APIs principais para acessar as entidades monitoradas por um DbContext.
- DbContext.Entry retorna uma EntityEntry<TEntity> instância para uma determinada instância de entidade.
- ChangeTracker.Entries retorna todas as instâncias de EntityEntry<TEntity> para todas as entidades rastreadas ou para todas as entidades rastreadas de um determinado tipo.
- DbContext.Find, DbContext.FindAsynce DbSet<TEntity>.FindDbSet<TEntity>.FindAsync encontre uma única entidade por chave primária, primeiro procurando em entidades controladas e, em seguida, consultando o banco de dados, se necessário.
- DbSet<TEntity>.Local retorna entidades reais (não instâncias de EntityEntry) para entidades do tipo de entidade representado pelo DbSet.
Cada uma delas é descrita com mais detalhes nas seções abaixo.
Dica
Este 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 todo o código neste documento baixando o código de exemplo do GitHub.
Usando instâncias DbContext.Entry e EntityEntry
Para cada entidade controlada, o EF Core (Entity Framework Core) mantém o controle de:
- O estado geral da entidade. Este é um dos
Unchanged,Modified,AddedouDeleted; consulte Controle de Alterações no EF Core para obter mais informações. - As relações entre entidades controladas. Por exemplo, o blog ao qual pertence uma postagem.
- Os "valores atuais" das propriedades.
- Os "valores originais" das propriedades, quando essas informações estão disponíveis. Os valores originais são os valores de propriedade que existiam quando a entidade foi consultada do banco de dados.
- Quais valores de propriedade foram modificados desde que foram consultados.
- Outras informações sobre valores de propriedade, como se o valor é temporário ou não.
Passar uma instância de entidade para DbContext.Entry resulta em um EntityEntry<TEntity> que fornece acesso a essas informações para a entidade fornecida. Por exemplo:
using var context = new BlogsContext();
var blog = await context.Blogs.SingleAsync(e => e.Id == 1);
var entityEntry = context.Entry(blog);
As seções a seguir mostram como usar um EntityEntry para acessar e manipular o estado da entidade, bem como o estado das propriedades e das navegaçãos da entidade.
Trabalhando com a entidade
O uso mais comum de EntityEntry<TEntity> é acessar o EntityState atual de uma entidade. Por exemplo:
var currentState = context.Entry(blog).State;
if (currentState == EntityState.Unchanged)
{
context.Entry(blog).State = EntityState.Modified;
}
O método Entry também pode ser usado em entidades que ainda não foram rastreadas. Isso não começa a acompanhar a entidade; o estado da entidade ainda é Detached. No entanto, o EntityEntry retornado pode então ser usado para alterar o estado da entidade, momento em que a entidade passará a ser rastreada no estado especificado. Por exemplo, o código a seguir começará a acompanhar uma instância 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);
Dica
Ao contrário do EF6, definir o estado de uma entidade individual não fará com que todas as entidades conectadas sejam acompanhadas. Isso torna a configuração do estado dessa forma uma operação de nível inferior à chamada Add, Attachou Update, que opera em um grafo inteiro de entidades.
A tabela a seguir resume maneiras de usar um EntityEntry para trabalhar com uma entidade inteira:
| Membro de EntityEntry | Descrição |
|---|---|
| EntityEntry.State | Obtém e define o EntityState da entidade. |
| EntityEntry.Entity | Obtém a instância da entidade. |
| EntityEntry.Context | O DbContext que está acompanhando essa entidade. |
| EntityEntry.Metadata | IEntityType metadados para o tipo de entidade. |
| EntityEntry.IsKeySet | Se a entidade teve ou não seu valor de chave definido. |
| EntityEntry.Reload() | Substitui os valores das propriedades por aqueles lidos do banco de dados. |
| EntityEntry.DetectChanges() | Obriga a detecção de alterações somente para essa entidade; consulte Detecção de Alterações e Notificações. |
Trabalhando com uma única propriedade
Várias sobrecargas de EntityEntry<TEntity>.Property permitem acesso às informações sobre uma propriedade individual de uma entidade. Por exemplo, usando uma API fortemente tipificada em estilo fluente:
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property(e => e.Name);
Em vez disso, o nome da propriedade pode ser passado como uma cadeia de caracteres. Por exemplo:
PropertyEntry<Blog, string> propertyEntry = context.Entry(blog).Property<string>("Name");
Em seguida, o retornado PropertyEntry<TEntity,TProperty> pode ser usado para acessar informações sobre a propriedade. Por exemplo, ele pode ser usado para obter e definir o valor atual da propriedade nesta entidade:
string currentValue = context.Entry(blog).Property(e => e.Name).CurrentValue;
context.Entry(blog).Property(e => e.Name).CurrentValue = "1unicorn2";
Ambos os métodos property usados acima retornam uma instância genérica PropertyEntry<TEntity,TProperty> fortemente tipada. O uso desse tipo genérico é preferencial porque permite o acesso a valores de propriedade sem tipos de valor boxing. No entanto, se o tipo de entidade ou propriedade não for conhecido em tempo de compilação, um não genérico PropertyEntry poderá ser obtido em vez disso:
PropertyEntry propertyEntry = context.Entry(blog).Property("Name");
Isso permite o acesso a informações de propriedade para qualquer propriedade, independentemente de seu tipo, ao custo de encapsulamento de tipos de valor. Por exemplo:
object blog = await context.Blogs.SingleAsync(e => e.Id == 1);
object currentValue = context.Entry(blog).Property("Name").CurrentValue;
context.Entry(blog).Property("Name").CurrentValue = "1unicorn2";
A tabela a seguir resume as informações de propriedade expostas por PropertyEntry:
| Membro PropertyEntry | Descrição |
|---|---|
| PropertyEntry<TEntity,TProperty>.CurrentValue | Obtém e define o valor atual da propriedade. |
| PropertyEntry<TEntity,TProperty>.OriginalValue | Obtém e define o valor original da propriedade, se disponível. |
| PropertyEntry<TEntity,TProperty>.EntityEntry | Uma referência de retorno à entidade EntityEntry<TEntity>. |
| PropertyEntry.Metadata | IProperty metadados da propriedade. |
| PropertyEntry.IsModified | Indica se essa propriedade está marcada como modificada e permite que esse estado seja alterado. |
| PropertyEntry.IsTemporary | Indica se essa propriedade está marcada como temporária e permite que esse estado seja alterado. |
Observações:
- O valor original de uma propriedade é o valor que a propriedade tinha quando a entidade foi consultada do banco de dados. No entanto, os valores originais não estarão disponíveis se a entidade foi desconectada e, em seguida, explicitamente anexada a outro DbContext, por exemplo, com
AttachouUpdate. Nesse caso, o valor original retornado será o mesmo que o valor atual. - SaveChanges só atualizará as propriedades marcadas como modificadas. Defina IsModified como true para forçar o EF Core a atualizar um determinado valor de propriedade ou defina-o como false para impedir que o EF Core atualize o valor da propriedade.
- Os valores temporários normalmente são gerados por geradores de valor EF Core. Definir o valor atual de uma propriedade substituirá o valor temporário pelo valor fornecido e marcará a propriedade como não temporária. Defina IsTemporary como true para forçar um valor a ser temporário mesmo depois de ter sido definido explicitamente.
Trabalhando com um único sistema de navegação
Várias sobrecargas de EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collection e EntityEntry.Navigation permitem acesso a informações sobre uma navegação específica.
As navegações de referência para uma única entidade relacionada são acessadas por meio dos métodos Reference. As navegações de referência apontam para os lados "um" das relações um-para-muitos e ambos os lados das relações um-a-um. Por exemplo:
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");
As navegações também podem ser coleções de entidades relacionadas quando usadas para os aspectos "muitos" em relações de um para muitos e de muitos para muitos. Os Collection métodos são usados para acessar as navegaçãos de coleção. Por exemplo:
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");
Algumas operações são comuns para todas as navegaçãos. Eles podem ser acessados para navegações de referência e coleção usando o método EntityEntry.Navigation. Observe que somente o acesso não genérico está disponível ao acessar todas as navegações juntas. Por exemplo:
NavigationEntry navigationEntry = context.Entry(blog).Navigation("Posts");
A tabela a seguir resume as maneiras de usar ReferenceEntry<TEntity,TProperty>eCollectionEntry<TEntity,TRelatedEntity>NavigationEntry:
| Membro NavigationEntry | Descrição |
|---|---|
| MemberEntry.CurrentValue | Obtém e define o valor atual da navegação. Esta é toda a coleção para navegações de coleções. |
| NavigationEntry.Metadata | INavigationBase metadados para a navegação. |
| NavigationEntry.IsLoaded | Obtém ou define um valor que indica se a entidade ou coleção relacionada foi totalmente carregada do banco de dados. |
| NavigationEntry.Load() | Carrega a entidade ou coleção relacionada do banco de dados; consulte o carregamento explícito de dados relacionados. |
| NavigationEntry.Query() | A consulta que o EF Core usaria para carregar essa navegação como um IQueryable, permitindo uma maior composição; consulte o carregamento explícito de dados relacionados. |
Trabalhando com todas as propriedades de uma entidade
EntityEntry.Properties retorna um IEnumerable<T> de PropertyEntry para cada propriedade da entidade. Isso pode ser usado para executar uma ação para cada propriedade da entidade. Por exemplo, para definir qualquer propriedade DateTime como DateTime.Now:
foreach (var propertyEntry in context.Entry(blog).Properties)
{
if (propertyEntry.Metadata.ClrType == typeof(DateTime))
{
propertyEntry.CurrentValue = DateTime.Now;
}
}
Além disso, EntityEntry contém vários métodos para obter e definir todos os valores de propriedade ao mesmo tempo. Esses métodos usam a PropertyValues classe, que representa uma coleção de propriedades e seus valores. PropertyValues pode ser obtido para valores atuais ou originais ou para os valores armazenados atualmente no banco de dados. Por exemplo:
var currentValues = context.Entry(blog).CurrentValues;
var originalValues = context.Entry(blog).OriginalValues;
var databaseValues = await context.Entry(blog).GetDatabaseValuesAsync();
Esses objetos PropertyValues não são muito úteis por conta própria. No entanto, eles podem ser combinados para executar operações comuns necessárias ao manipular entidades. Isso é útil ao trabalhar com objetos de transferência de dados e ao resolver conflitos de simultaneidade otimistas. As seções a seguir mostram alguns exemplos.
Definindo valores atuais ou originais de uma entidade ou DTO
Os valores atuais ou originais de uma entidade podem ser atualizados copiando valores de outro objeto. Por exemplo, considere um BlogDto DTO (objeto de transferência de dados) com as mesmas propriedades que o tipo de entidade:
public class BlogDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Isso pode ser usado para definir os valores atuais de uma entidade controlada usando PropertyValues.SetValues:
var blogDto = new BlogDto { Id = 1, Name = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDto);
Às vezes, essa técnica é usada ao atualizar uma entidade com valores obtidos de uma chamada de serviço ou de um cliente em um aplicativo de n camadas. Observe que o objeto usado não precisa ser do mesmo tipo que a entidade, desde que tenha propriedades cujos nomes correspondam aos da entidade. No exemplo acima, uma instância do DTO BlogDto é usada para definir os valores atuais de uma entidade controlada Blog .
Observe que as propriedades serão marcadas apenas como modificadas se o conjunto de valores for diferente do valor atual.
Definindo valores atuais ou originais de um dicionário
O exemplo anterior define valores de uma entidade ou instância de DTO. O mesmo comportamento está disponível quando os valores de propriedade são armazenados como pares nome/valor em um dicionário. Por exemplo:
var blogDictionary = new Dictionary<string, object> { ["Id"] = 1, ["Name"] = "1unicorn2" };
context.Entry(blog).CurrentValues.SetValues(blogDictionary);
Definindo valores atuais ou originais do banco de dados
Os valores atuais ou originais de uma entidade podem ser atualizados com os valores mais recentes do banco de dados chamando GetDatabaseValues() ou GetDatabaseValuesAsync usando o objeto retornado para definir valores atuais ou originais, ou ambos. Por exemplo:
var databaseValues = await context.Entry(blog).GetDatabaseValuesAsync();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);
Criando um objeto clonado que contém valores atuais, originais ou de banco de dados
O objeto PropertyValues retornado de CurrentValues, OriginalValues ou GetDatabaseValues pode ser usado para criar um clone da entidade usando PropertyValues.ToObject(). Por exemplo:
var clonedBlog = (await context.Entry(blog).GetDatabaseValuesAsync()).ToObject();
Observe que ToObject retorna uma nova instância que não é controlada pelo DbContext. O objeto retornado também não tem nenhuma relação definida para outras entidades.
O objeto clonado pode ser útil para resolver problemas relacionados a atualizações simultâneas para o banco de dados, especialmente quando os dados são associados a objetos de um determinado tipo. Consulte concorrência otimista para obter mais informações.
Trabalhando com todas as navegações de uma entidade
EntityEntry.Navigations retorna um IEnumerable<T> de NavigationEntry para cada navegação da entidade. EntityEntry.References e EntityEntry.Collections fazem a mesma coisa, mas restritos a navegações de referência ou coleção, respectivamente. Isso pode ser usado para executar uma ação para cada navegação da entidade. Por exemplo, para forçar o carregamento de todas as entidades relacionadas:
foreach (var navigationEntry in context.Entry(blog).Navigations)
{
navigationEntry.Load();
}
Trabalhando com todos os membros de uma entidade
Propriedades regulares e propriedades de navegação têm um estado e um comportamento diferentes. Portanto, é comum processar navegações e não navegações separadamente, conforme mostrado nas seções acima. No entanto, às vezes pode ser útil fazer algo com qualquer membro da entidade, independentemente de ser uma propriedade ou navegação regular. EntityEntry.Member e EntityEntry.Members são fornecidos para essa finalidade. Por exemplo:
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}");
}
A execução desse código em um blog do exemplo gera a seguinte saída:
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]
Dica
A visualização de debug do rastreador de alterações mostra informações como esta. A exibição de depuração de todo o rastreamento de alterações é gerada a partir da instância EntityEntry.DebugView de cada entidade rastreada.
Localizar e LocalizarAsync
DbContext.Find, DbContext.FindAsynce DbSet<TEntity>.FindDbSet<TEntity>.FindAsync são projetados para pesquisa eficiente de uma única entidade quando sua chave primária é conhecida. Find primeiro verifica se a entidade já está acompanhada e, em caso afirmativo, retorna a entidade imediatamente. Uma consulta de banco de dados só será feita se a entidade não for rastreada localmente. Por exemplo, considere este código que chama Find duas vezes para a mesma entidade:
using var context = new BlogsContext();
Console.WriteLine("First call to Find...");
var blog1 = await context.Blogs.FindAsync(1);
Console.WriteLine($"...found blog {blog1.Name}");
Console.WriteLine();
Console.WriteLine("Second call to Find...");
var blog2 = await context.Blogs.FindAsync(1);
Debug.Assert(blog1 == blog2);
Console.WriteLine("...returned the same instance without executing a query.");
A saída desse código (incluindo o registro em log do EF Core) ao usar SQLite é:
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 a primeira chamada não localiza a entidade localmente e, portanto, executa uma consulta de banco de dados. Por outro lado, a segunda chamada retorna a mesma instância sem consultar o banco de dados porque ele já está sendo rastreado.
Localizar retornará nulo caso uma entidade com a chave fornecida não seja acompanhada localmente e não exista no banco de dados.
Chaves compostas
Localizar também pode ser usado com chaves compostas. Por exemplo, considere uma OrderLine entidade com uma chave composta que consiste na ID do pedido e na ID do produto:
public class OrderLine
{
public int OrderId { get; set; }
public int ProductId { get; set; }
//...
}
A chave composta deve ser configurada DbContext.OnModelCreating para definir as partes-chave e sua ordem. Por exemplo:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<OrderLine>()
.HasKey(e => new { e.OrderId, e.ProductId });
}
Observe que OrderId essa é a primeira parte da chave e ProductId é a segunda parte da chave. Essa ordem deve ser usada ao passar valores de chave para Localizar. Por exemplo:
var orderline = await context.OrderLines.FindAsync(orderId, productId);
Usando ChangeTracker.Entries para acessar todas as entidades controladas
Até agora, acessamos apenas um único EntityEntry de cada vez. ChangeTracker.Entries() retorna um EntityEntry para cada entidade atualmente acompanhada pelo DbContext. Por exemplo:
using var context = new BlogsContext();
var blogs = await context.Blogs.Include(e => e.Posts).ToListAsync();
foreach (var entityEntry in context.ChangeTracker.Entries())
{
Console.WriteLine($"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property("Id").CurrentValue}");
}
Esse código gera a seguinte saída:
Found Blog entity with ID 1
Found Post entity with ID 1
Found Post entity with ID 2
Observe que as entradas para tanto blogs quanto postagens são retornadas. Em vez disso, os resultados podem ser filtrados para um tipo de entidade específico usando a ChangeTracker.Entries<TEntity>() sobrecarga genérica:
foreach (var entityEntry in context.ChangeTracker.Entries<Post>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
A saída desse código mostra que somente as postagens são retornadas:
Found Post entity with ID 1
Found Post entity with ID 2
Além disso, o uso da sobrecarga genérica retorna instâncias genéricas EntityEntry<TEntity>. Isso é o que permite o acesso fluente à Id propriedade neste exemplo.
O tipo genérico usado para filtragem não precisa ser um tipo de entidade mapeada; em vez disso, um tipo base ou interface não mapeado pode ser usado. Por exemplo, se todos os tipos de entidade no modelo implementarem uma interface definindo sua propriedade de chave:
public interface IEntityWithKey
{
int Id { get; set; }
}
Em seguida, essa interface pode ser usada para trabalhar com a chave de qualquer entidade controlada de maneira com tipagem forte. Por exemplo:
foreach (var entityEntry in context.ChangeTracker.Entries<IEntityWithKey>())
{
Console.WriteLine(
$"Found {entityEntry.Metadata.Name} entity with ID {entityEntry.Property(e => e.Id).CurrentValue}");
}
Usando DbSet.Local para consultar entidades controladas
As consultas EF Core são sempre executadas no banco de dados e retornam apenas entidades que foram salvas no banco de dados. DbSet<TEntity>.Local fornece um mecanismo para consultar o DbContext sobre entidades locais e rastreadas.
Como DbSet.Local é usado para consultar entidades controladas, é comum carregar entidades no DbContext e, em seguida, trabalhar com elas. Isso é especialmente verdadeiro para associação de dados, mas também pode ser útil em outras situações. Por exemplo, no código a seguir, o banco de dados é consultado pela primeira vez para todos os blogs e postagens. O Load método de extensão é usado para executar essa consulta, com os resultados sendo acompanhados pelo contexto, sem que sejam retornados diretamente ao aplicativo. (Usar ToList ou similar tem o mesmo efeito, mas com a sobrecarga de criar a lista retornada, o que não é necessário aqui.) O exemplo em seguida utiliza DbSet.Local para acessar as entidades rastreadas localmente:
using var context = new BlogsContext();
await context.Blogs.Include(e => e.Posts).LoadAsync();
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, ao contrário ChangeTracker.Entries(), DbSet.Local retorna instâncias de entidade diretamente. Um EntityEntry pode, é claro, sempre ser obtido para a entidade retornada chamando DbContext.Entry.
A visão local
DbSet<TEntity>.Local retorna uma visualização de entidades monitoradas localmente que reflete o estado atual dessas entidades. Especificamente, isso significa que:
-
Addedentidades estão incluídas. Observe que esse não é o caso de consultas EF Core normais, poisAddedas entidades ainda não existem no banco de dados e, portanto, nunca são retornadas por uma consulta de banco de dados. -
Deletedentidades são excluídas. Observe que esse não é novamente o caso de consultas EF Core normais, jáDeletedque as entidades ainda existem no banco de dados e, portanto, são retornadas por consultas de banco de dados.
Tudo isso significa que DbSet.Local é uma exibição sobre os dados que reflete o estado conceitual atual do grafo de entidade, com Added entidades incluídas e Deleted entidades excluídas. Isso corresponde ao estado de banco de dados que deve ser depois que SaveChanges é chamado.
Normalmente, essa é a exibição ideal para associação de dados, pois apresenta ao usuário os dados conforme eles os entendem com base nas alterações feitas pelo aplicativo.
O código a seguir demonstra isso marcando uma postagem como Deleted e, em seguida, adicionando uma nova postagem, marcando-a como Added:
using var context = new BlogsContext();
var posts = await context.Posts.Include(e => e.Blog).ToListAsync();
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}");
}
A saída desse código é:
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 a postagem excluída é removida do modo de exibição local e a postagem adicionada está incluída.
Usando o Local para adicionar e remover entidades
DbSet<TEntity>.Local retorna uma instância de LocalView<TEntity>. Esta é uma implementação de ICollection<T> que gera e responde a notificações quando entidades são adicionadas e removidas da coleção. (Este é o mesmo conceito que ObservableCollection<T>, mas implementado como uma projeção sobre as entradas existentes de rastreamento de alterações do EF Core, em vez de como uma coleção independente.)
As notificações do modo de exibição local são conectadas ao controle de alterações DbContext de modo que o modo de exibição local permaneça em sincronia com o DbContext. Especificamente:
- Adicionando uma nova entidade a
DbSet.Localfaz com que ela seja rastreada pelo DbContext, normalmente no estadoAdded. (Se a entidade já tiver um valor de chave gerado, ela será controlada comoUnchangedem vez disso.) - Remover uma entidade de
DbSet.Localfaz com que ela seja marcada comoDeleted. - Uma entidade que se torna rastreada pelo DbContext aparecerá automaticamente na coleção
DbSet.Local. Por exemplo, executar uma consulta para trazer mais entidades automaticamente resulta na atualização da vista local. - Uma entidade marcada como
Deletedserá removida da coleção local automaticamente.
Isso significa que a visualização local pode ser usada para manipular entidades monitoradas simplesmente adicionando e removendo da coleção. Por exemplo, vamos modificar o código de exemplo anterior para adicionar e remover postagens da coleção local:
using var context = new BlogsContext();
var posts = await context.Posts.Include(e => e.Blog).ToListAsync();
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}");
}
A saída permanece inalterada em relação ao exemplo anterior porque as alterações feitas no modo de exibição local são sincronizadas com o DbContext.
Usando a visualização local para a associação de dados do Windows Forms ou WPF.
DbSet<TEntity>.Local forma a base para associação de dados a entidades do EF Core. No entanto, Windows Forms e WPF funcionam melhor quando usados com o tipo específico de coleção notificante que eles esperam. O modo de exibição local dá suporte à criação desses tipos de coleção específicos:
- LocalView<TEntity>.ToObservableCollection() retorna uma vinculação ObservableCollection<T> para dados WPF.
- LocalView<TEntity>.ToBindingList() retorna um BindingList<T> para associação de dados do Windows Forms.
Por exemplo:
ObservableCollection<Post> observableCollection = context.Posts.Local.ToObservableCollection();
BindingList<Post> bindingList = context.Posts.Local.ToBindingList();
Consulte Introdução ao WPF para obter mais informações sobre a associação de dados do WPF com o EF Core e introdução aos Windows Forms para obter mais informações sobre a associação de dados do Windows Forms com o EF Core.
Dica
A vista local de uma instância de DbSet é criada de maneira diferida quando acessada pela primeira vez e, em seguida, armazenada em cache. A criação localView em si é rápida e não usa memória significativa. No entanto, ele chama DetectChanges, o que pode ser lento para um grande número de entidades. As coleções criadas por ToObservableCollection e ToBindingList também são criadas de forma preguiçosa e, em seguida, armazenadas em cache. Esses dois métodos criam novas coleções, que podem ser lentas e usar muita memória quando milhares de entidades estão envolvidas.