Partilhar via


Acesso a entidades controladas

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

Cada um deles é descrito em mais detalhes nas seções abaixo.

Sugestão

Este documento pressupõe que os estados da entidade e os conceitos básicos 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.

Sugestão

Você pode executar e depurar todo o código neste documento baixando o código de exemplo do GitHub.

Utilização de instâncias de DbContext.Entry e EntityEntry

Para cada entidade controlada, o Entity Framework Core (EF Core) controla:

  • O estado geral da entidade. Este é um dos Unchanged, Modified, Added, ou Deleted; consulte Controlo de alterações no EF Core para obter mais informações.
  • As relações entre entidades rastreadas. Por exemplo, o blog ao qual pertence uma postagem.
  • Os "valores atuais" dos imóveis.
  • Os "valores originais" dos imóveis, quando esta informação estiver disponível. Os valores originais são os valores de propriedade que existiam quando a entidade foi consultada a partir 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 uma EntityEntry<TEntity> que fornece acesso a esta informação para a entidade em questão. 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 navegações da entidade.

Trabalhar com a entidade

O uso mais comum de EntityEntry<TEntity> é para 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 são rastreadas. Isso não inicia o rastreamento da entidade; o estado da entidade ainda é Detached. No entanto, o EntityEntry retornado pode ser usado para alterar o estado da entidade, momento em que a entidade será rastreada no estado determinado. Por exemplo, o código a seguir começará a rastrear 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);

Sugestão

Ao contrário do EF6, definir o estado de uma entidade individual não fará com que todas as entidades conectadas sejam rastreadas. Isso torna a configuração do estado dessa maneira uma operação de nível inferior à chamada Add, Attach ou Update, que operam em um gráfico 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 o EntityState da entidade.
EntityEntry.Entity Obtém a instância da entidade.
EntityEntry.Context O DbContext que está rastreando essa entidade.
EntityEntry.Metadata IEntityType metadados para o tipo de entidade.
EntityEntry.IsKeySet Se a entidade teve ou não definido o seu valor-chave.
EntityEntry.Reload() Substitui valores de propriedade por valores lidos do banco de dados.
EntityEntry.DetectChanges() Força a deteção de alterações apenas para esta entidade; consulte Deteção de alterações e notificações.

Trabalhar com uma única propriedade

Várias sobrecargas de EntityEntry<TEntity>.Property permitem o 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");

O devolvido PropertyEntry<TEntity,TProperty> pode então ser usado para aceder a 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> de tipagem forte. O uso desse tipo genérico é preferível porque permite o acesso a valores de propriedade sem tipos de valor de boxe. No entanto, se o tipo de entidade ou propriedade não for conhecido em tempo de compilação, então um não-genérico PropertyEntry pode ser obtido:

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

Isso permite o acesso às informações de propriedade de qualquer propriedade, independentemente de seu tipo, às custas dos tipos de valor de boxe. 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 ao EntityEntry<TEntity> para a entidade.
PropertyEntry.Metadata IProperty metadados para a 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 atualizará apenas 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 são normalmente 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 explicitamente definido.

Trabalhar com uma única navegação

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

As navegações de referência para uma única entidade relacionada são acessadas através dos métodos Reference. As navegações de referência apontam para os lados "um" das relações um-para-muitos, assim como para ambos os lados das relações 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 "muitos" lados de relações um-para-muitos e muitos-para-muitos. Os Collection métodos são usados para acessar as navegações 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 a todas as navegações. Eles podem ser acessados para navegações de referência e coleção usando o EntityEntry.Navigation método. Observe que apenas 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. Esta é a coleção completa para navegação 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 relacionada ou coleção do banco de dados; consulte 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 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 objeto de transferência de dados (DTO) com as mesmas propriedades do 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);

Essa técnica às vezes é usada ao atualizar uma entidade com valores obtidos de uma chamada de serviço ou de um cliente numa aplicação n-tier. 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 só serão marcadas como modificadas se o conjunto de valores for diferente do valor atual.

Definir valores atuais ou originais a partir de um dicionário

O exemplo anterior define valores de uma entidade ou instância 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 contendo 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 é rastreada pelo DbContext. O objeto retornado também não tem nenhum relacionamento definido para outras entidades.

O objeto clonado pode ser útil para resolver problemas relacionados a atualizações simultâneas do banco de dados, especialmente quando os dados são vinculados a objetos de um determinado tipo. Consulte concorrência otimista para obter mais informações.

Lidar 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 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();
}

Trabalhar com todos os membros de uma entidade

As propriedades regulares e as propriedades de navegação têm estado e comportamento diferentes. Portanto, é comum processar navegações e não navegações separadamente, como 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 navegacional. EntityEntry.Member e EntityEntry.Members são fornecidos para este fim. 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}");
}

Executar esse 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]

Sugestão

A visualização de depuração do rastreador de alterações exibe informações como esta. A vista de depuração para todo o rastreador de alterações é gerada a partir do componente EntityEntry.DebugView de cada entidade rastreada.

Localizar e FindAsync

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. Encontre as primeiras verificações se a entidade já estiver rastreada e, em caso afirmativo, devolve a entidade imediatamente. Uma consulta de banco de dados só é feita se a entidade não for rastreada localmente. Por exemplo, considere este código que chama a função 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 log do EF Core) ao usar o 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.

Find retorna null se uma entidade com a chave fornecida não for rastreada localmente e não existir no banco de dados.

Chaves compostas

Find também pode ser usado com chaves compostas. Por exemplo, considere uma OrderLine entidade com uma chave composta que consiste no ID do pedido e no ID do produto:

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

    //...
}

A chave composta deve ser configurada em DbContext.OnModelCreating para definir as partes da chave e a sua ordem. Por exemplo:

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

Observe que OrderId é a primeira parte da chave e ProductId é a segunda parte da chave. Essa ordem deve ser usada ao passar valores de chave para Find. Por exemplo:

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

Usando ChangeTracker.Entries para acessar todas as entidades controladas

Até agora só acedemos a uma única 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}");
}

Este 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

Note que as entradas para blogs e publicações 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 deste código mostra que apenas as postagens são retornadas:

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

Além disso, a utilização da sobrecarga genérica retorna instâncias genéricas EntityEntry<TEntity>. Isso é o que permite esse 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, pode ser usado um tipo de base ou interface não mapeada. 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 rastreada de uma 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 somente retornam entidades que foram salvas no banco de dados. DbSet<TEntity>.Local fornece um mecanismo para consultar o DbContext para entidades locais controladas.

Como DbSet.Local é usado para consultar entidades controladas, é típico carregar entidades no DbContext e, em seguida, trabalhar com essas entidades carregadas. Isso é especialmente verdadeiro para a vinculaçã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 primeiro para todos os blogs e posts. O método de extensão Load é usado para executar esta consulta com os resultados acompanhados pelo contexto sem serem retornados diretamente à aplicação. (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 usa 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 de ChangeTracker.Entries(), DbSet.Local retorna as instâncias de entidade diretamente. Um EntityEntry pode, é claro, sempre ser obtido para a entidade retornada chamando DbContext.Entry.

A perspetiva local

DbSet<TEntity>.Local Retorna uma exibição de entidades rastreadas localmente que reflete o atual EntityState dessas entidades. Concretamente, isto significa que:

  • Added entidades estão incluídas. Observe que esse não é o caso para consultas EF Core normais, uma vez Added que as entidades ainda não existem no banco de dados e, portanto, nunca são retornadas por uma consulta de banco de dados.
  • Deleted as entidades estão excluídas. Observe que esse novamente não é o caso para consultas EF Core normais, uma vez Deleted que 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 visão sobre os dados que reflete o estado conceitual atual do gráfico de entidades, com Added entidades incluídas e Deleted entidades excluídas. Isso corresponde ao estado esperado do banco de dados após SaveChanges ser chamado.

Esta é normalmente a visualização ideal para a vinculação de dados, uma vez que apresenta ao usuário os dados como 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 deste 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 foi removida da visualização local e a postagem adicionada foi incluída.

Usando 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 notificações e responde a elas quando entidades são adicionadas e removidas da coleção. (Este é o mesmo conceito do 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 do DbContext para que o modo de exibição local permaneça sincronizado com o DbContext. Mais especificamente:

  • Ao adicionar uma nova entidade, DbSet.Local provoca que seja rastreada pelo DbContext, normalmente no estado Added. (Se a entidade já tiver um valor de chave gerado, ele será rastreado 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 rastreada pelo DbContext aparecerá automaticamente na coleção DbSet.Local. Por exemplo, executar uma consulta para trazer mais entidades automaticamente faz com que a exibição local seja atualizada.
  • Uma entidade marcada como Deleted será removida da coleção local automaticamente.

Isso significa que a vista local pode ser usada para manipular entidades rastreáveis 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 na exibição local são sincronizadas com o DbContext.

Usando o modo de exibição local para vinculação de dados do Windows Forms ou WPF

DbSet<TEntity>.Local forma a base para a vinculação de dados às entidades EF Core. No entanto, o Windows Forms e o WPF funcionam melhor quando usados com o tipo específico de coleção de notificação que eles esperam. A vista local suporta a criação destes 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 vinculação de dados do WPF com o EF Core e Introdução ao Windows Forms para obter mais informações sobre a vinculação de dados do Windows Forms com o EF Core.

Sugestão

A exibição local para uma determinada instância DbSet é criada preguiçosamente quando acessada pela primeira vez e, em seguida, armazenada em cache. A criação do LocalView em si é rápida e não usa memória significativa. No entanto, 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 preguiçosamente e depois armazenadas em cache. Ambos os métodos criam novas coleções, que podem ser lentas e usar muita memória quando milhares de entidades estão envolvidas.