Partilhar via


Acessando entidades controladas

Há quatro APIs principais para acessar entidades controladas por um DbContext:

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 em 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, Added ou Deleted; 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 resultar em um EntityEntry<TEntity> fornecimento de acesso a essas informações para a entidade fornecida. Por exemplo:

using var context = new BlogsContext();

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

As seções a seguir mostram como usar uma EntityEntry para acessar e manipular o estado da entidade, bem como o estado das propriedades e navegações da entidade.

Trabalhando com a entidade

O uso mais comum de EntityEntry<TEntity> é acessar a corrente EntityState 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 será controlada 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 controladas. 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 EntityEntry Descrição
EntityEntry.State Obtém e define a 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 valores de propriedade com valores lidos do banco de dados.
EntityEntry.DetectChanges() Força 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 permitir acesso a informações sobre uma propriedade individual de uma entidade. Por exemplo, usando uma API de tipo forte e 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> com tipagem forte. 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, às custas dos tipos de valor boxing. Por exemplo:

object blog = context.Blogs.Single(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 back para a EntityEntry<TEntity> para a entidade.
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 Attach ou Update. 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 uma única navegação

Várias sobrecargas de EntityEntry<TEntity>.Reference, EntityEntry<TEntity>.Collection e EntityEntry.Navigation permitir acesso a informações sobre uma navegação individual.

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" dos relacionamentos um-para-muitos e para ambos os lados dos relacionamentos um-para-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 lados "muitos" dos relacionamentos um-para-muitos e muitos-para-muitos. Os métodos Collection são usados para acessar as navegações da 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 a todas as navegações. Eles podem ser acessados para navegação de referência e de 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>, CollectionEntry<TEntity,TRelatedEntity> e NavigationEntry:

Membro NavigationEntry Descrição
MemberEntry.CurrentValue Obtém e define o valor atual da navegação. Essa é a coleção completa para as navegações da coleção.
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 uma IQueryable que pode ser composta ainda mais; 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 classe PropertyValues, 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 = context.Entry(blog).GetDatabaseValues();

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 Blog controlada.

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 = context.Entry(blog).GetDatabaseValues();
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 = context.Entry(blog).GetDatabaseValues().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 a simultaneidade otimista para obter mais informações.

Trabalhar 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 às navegações de referência ou de 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 as navegações e as 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 regular ou de navegação. 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 exibição de depuração do rastreador de alterações mostra informações como esta. A exibição de depuração de todo o rastreador de alterações é gerada a partir do indivíduo EntityEntry.DebugView de cada entidade controlada.

Localizar e LocalizarAsync

DbContext.Find, DbContext.FindAsync, DbSet<TEntity>.Find e DbSet<TEntity>.FindAsync são projetados para pesquisa eficiente de uma única entidade quando sua chave primária é conhecida. Localize as primeiras verificações se a entidade já está controlada 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 esse código que chama Localizar duas vezes para a mesma entidade:

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.");

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 se uma entidade com a chave fornecida não for controlada localmente e não existir no banco de dados.

Chaves compostas

Localizar também pode ser usado com chaves compostas. Por exemplo, considere uma entidade OrderLine 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 = context.OrderLines.Find(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 controlada pelo DbContext. Por exemplo:

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}");
}

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 blogs e postagens são retornadas. Em vez disso, os resultados podem ser filtrados para um tipo de entidade específico usando a sobrecarga genérica 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}");
}

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 à propriedade Id 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 fortemente tipada. 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 para entidades locais e controladas.

Como DbSet.Local é usado para consultar entidades controladas, é comum carregar entidades no DbContext e, em seguida, trabalhar com essas entidades carregadas. 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 método de extensão Load é usado para executar essa consulta com os resultados acompanhados pelo contexto sem ser retornado diretamente ao aplicativo. (Usar ToList ou similar tem o mesmo efeito, mas com a sobrecarga de criar a lista retornada, que não é necessária aqui.) Em seguida, o exemplo usa DbSet.Local para acessar as entidades rastreadas localmente:

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

O modo de exibição local

DbSet<TEntity>.Local retorna uma exibição de entidades controladas localmente que refletem a corrente EntityState dessas entidades. Especificamente, isso significa que:

  • Added entidades estão incluídas. Observe que esse não é o caso de consultas EF Core normais, pois Added as entidades ainda não existem no banco de dados e, portanto, nunca são retornadas por uma consulta de banco de dados.
  • Deleted entidades são excluídas. Observe que esse não é novamente o caso de consultas EF Core normais, já que as entidades Deleted ainda existem no banco de dados e, portanto, são retornadas por consultas de banco de dados.

Tudo isso significa que DbSet.Local é a exibição sobre os dados que refletem o estado conceitual atual do grafo de entidade, com entidades incluídas Added e entidades excluída Deleteds. 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 = 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}");
}

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>. Essa é uma implementação de ICollection<T> que gera e responde às notificações quando as entidades são adicionadas e removidas da coleção. (Esse é o mesmo conceito como ObservableCollection<T>, mas implementado como uma projeção sobre entradas de controle de alterações existentes 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:

  • Adicionar uma nova entidade faz com DbSet.Local que ela seja rastreada pelo DbContext, normalmente no estado Added. (Se a entidade já tiver um valor de chave gerado, ela será controlada como Unchanged em vez disso.)
  • A remoção de uma entidade de DbSet.Local faz com que ela seja marcada como Deleted.
  • Uma entidade que se torna controlada pelo DbContext aparecerá automaticamente na coleção DbSet.Local. Por exemplo, a execução de uma consulta para trazer mais entidades automaticamente faz com que o modo de exibição local seja atualizado.
  • Uma entidade marcada como Deleted será removida da coleção local automaticamente.

Isso significa que o modo de exibição local pode ser usado para manipular entidades controladas 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 = 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}");
}

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 exibição local para Windows Forms ou associação de dados do WPF

DbSet<TEntity>.Local forma a base para associação de dados a entidades do EF Core. No entanto, os Windows Forms e o WPF funcionam melhor quando usados com o tipo específico de notificar a coleção que eles esperam. O modo de exibição local dá suporte à criação desses tipos de coleção específicos:

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

O modo de exibição local de uma determinada instância de DbSet é criado de forma preguiçosa quando acessado pela primeira vez e, em seguida, armazenado 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.