Доступ к отслеживаемых сущностям

Существует четыре основных API для доступа к сущностям, отслеживаемым по DbContext:

  • DbContext.EntryEntityEntry<TEntity> возвращает экземпляр для данного экземпляра сущности.
  • ChangeTracker.Entries возвращает EntityEntry<TEntity> экземпляры для всех отслеживаемых сущностей или для всех отслеживаемых сущностей заданного типа.
  • DbContext.Find, , DbContext.FindAsyncDbSet<TEntity>.Findи DbSet<TEntity>.FindAsync найти одну сущность по первичному ключу, сначала искать отслеживаемые сущности, а затем запрашивать базу данных при необходимости.
  • DbSet<TEntity>.Local возвращает фактические сущности (а не экземпляры EntityEntry) для сущностей типа сущности, представленного DbSet.

Каждый из них подробно описан в разделах ниже.

Совет

В этом документе предполагается, что состояния сущности и основы отслеживания изменений EF Core понятны. Дополнительные сведения об этих разделах см. в Отслеживание изменений в EF Core.

Совет

Вы можете запустить и отладить весь код, используемый в этой документации, скачав пример кода из GitHub.

Использование экземпляров DbContext.Entry и EntityEntry

Для каждой отслеживаемой сущности Entity Framework Core (EF Core) отслеживает:

  • Общее состояние сущности. Это один из Unchanged, ModifiedAddedили Deleted; см. Отслеживание изменений в EF Core для получения дополнительных сведений.
  • Связи между отслеживаемых сущностей. Например, блог, к которому принадлежит запись.
  • Текущие значения свойств.
  • Исходные значения свойств, когда эта информация доступна. Исходные значения — это значения свойств, которые существовали при запросе сущности из базы данных.
  • Какие значения свойств были изменены, так как они были запрошены.
  • Другие сведения о значениях свойств, например о том, является ли значение временным.

Передача экземпляра DbContext.Entry сущности в EntityEntry<TEntity> результаты предоставления доступа к этой информации для данной сущности. Например:

using var context = new BlogsContext();

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

В следующих разделах показано, как использовать EntityEntry для доступа к состоянию сущности и управления ими, а также состояния свойств и навигаций сущности.

Работа с сущностью

Наиболее распространенным способом EntityEntry<TEntity> является доступ к текущему объекту EntityState . Например:

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

Метод Entry также можно использовать для сущностей, которые еще не отслеживаются. Это не запускает отслеживание сущности; состояние сущности по-прежнему Detached. Однако возвращаемый EntityEntry можно затем использовать для изменения состояния сущности, в какой момент сущность будет отслеживаться в заданном состоянии. Например, следующий код начнет отслеживать экземпляр блога следующим 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);

Совет

В отличие от EF6, установка состояния отдельной сущности не приведет к отслеживанию всех подключенных сущностей. Это делает настройку состояния таким образом, чтобы операция более низкого уровня, чем вызов Add, Attachили Update, которая работала на всем графе сущностей.

В следующей таблице приведены способы использования EntityEntry для работы со всей сущностью:

Элемент EntityEntry Description
EntityEntry.State Возвращает и задает EntityState сущность.
EntityEntry.Entity Возвращает экземпляр сущности.
EntityEntry.Context Объект DbContext отслеживания этой сущности.
EntityEntry.Metadata IEntityType метаданные для типа сущности.
EntityEntry.IsKeySet Имеет ли сущность значение ключа или нет.
EntityEntry.Reload() Перезаписывает значения свойств со значениями, считываемыми из базы данных.
EntityEntry.DetectChanges() Принудительное обнаружение изменений только для этой сущности; см. раздел "Обнаружение изменений" и "Уведомления".

Работа с одним свойством

Несколько перегрузок разрешают доступ к сведениям EntityEntry<TEntity>.Property об отдельном свойстве сущности. Например, с помощью строго типизированного API с типизированным типом:

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

Вместо этого имя свойства можно передать в виде строки. Например:

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

Затем возвращаемый PropertyEntry<TEntity,TProperty> объект можно использовать для доступа к сведениям о свойстве. Например, его можно использовать для получения и задания текущего значения свойства в этой сущности:

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

Оба метода свойства, используемые выше, возвращают строго типизированный универсальный PropertyEntry<TEntity,TProperty> экземпляр. Использование этого универсального типа предпочтительнее, так как оно позволяет получить доступ к значениям свойств без типов значений бокса. Однако если тип сущности или свойства не известен во время компиляции, вместо этого можно получить не универсальный PropertyEntry код:

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

Это позволяет получить доступ к сведениям о свойстве для любого свойства независимо от его типа, за счет типов значений бокса. Например:

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

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

В следующей таблице приведены сведения о свойстве, предоставляемые PropertyEntry:

Элемент PropertyEntry Description
PropertyEntry<TEntity,TProperty>.CurrentValue Возвращает и задает текущее значение свойства.
PropertyEntry<TEntity,TProperty>.OriginalValue Возвращает и задает исходное значение свойства, если оно доступно.
PropertyEntry<TEntity,TProperty>.EntityEntry Обратная ссылка на EntityEntry<TEntity> сущность.
PropertyEntry.Metadata IProperty метаданные свойства.
PropertyEntry.IsModified Указывает, помечено ли это свойство как измененное, и позволяет изменить это состояние.
PropertyEntry.IsTemporary Указывает, помечено ли это свойство как временное, и позволяет изменить это состояние.

Примечания.

  • Исходное значение свойства — это значение, которое было у свойства при запросе сущности из базы данных. Однако исходные значения недоступны, если сущность была отключена, а затем явно присоединена к другому DbContext, например с Attach или Update. В этом случае возвращаемое исходное значение будет совпадать с текущим значением.
  • SaveChanges будет обновлять только свойства, помеченные как измененные. Задайте IsModified значение true для принудительного обновления значения свойства EF Core или задайте значение false, чтобы предотвратить обновление значения свойства EF Core.
  • Временные значения обычно создаются генераторами значений EF Core. Установка текущего значения свойства заменит временное значение заданным значением и помечает свойство как временное. Задайте IsTemporary значение true, чтобы принудительно задать временное значение даже после явного задания.

Работа с одной навигацией

Несколько перегрузок EntityEntry<TEntity>.Referenceи EntityEntry<TEntity>.CollectionEntityEntry.Navigation разрешение доступа к информации об отдельной навигации.

Доступ к ссылочным навигациям к одной связанной сущности обращается с помощью Reference методов. Ссылочные навигации указывают на "одну" стороны отношений "один ко многим" и обе стороны связей "один ко многим". Например:

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

Навигации также могут быть коллекциями связанных сущностей при использовании для "многих" сторон отношений "один ко многим" и "многие ко многим". Методы Collection используются для доступа к навигациям коллекции. Например:

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

Некоторые операции являются общими для всех навигаций. К ней EntityEntry.Navigation можно получить доступ как для ссылочных, так и для коллекций навигаций с помощью метода. Обратите внимание, что при совместном доступе ко всем навигациям доступен только не универсальный доступ. Например:

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

В следующей таблице приведены способы использования ReferenceEntry<TEntity,TProperty>иCollectionEntry<TEntity,TRelatedEntity>NavigationEntry:

Элемент NavigationEntry Description
MemberEntry.CurrentValue Возвращает и задает текущее значение навигации. Это вся коллекция для навигаций по коллекциям.
NavigationEntry.Metadata INavigationBase метаданные для навигации.
NavigationEntry.IsLoaded Возвращает или задает значение, указывающее, была ли связанная сущность или коллекция полностью загружена из базы данных.
NavigationEntry.Load() Загружает связанную сущность или коллекцию из базы данных; См . явную загрузку связанных данных.
NavigationEntry.Query() Запрос EF Core будет использоваться для загрузки этой навигации в качестве дополнительного IQueryable создания; см . сведения о явной загрузке связанных данных.

Работа со всеми свойствами сущности

EntityEntry.Properties возвращает значение IEnumerable<T>PropertyEntry для каждого свойства сущности. Это можно использовать для выполнения действия для каждого свойства сущности. Например, чтобы задать для любого свойства DateTime значение DateTime.Now:

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

Кроме того, EntityEntry содержит несколько методов для получения и задания всех значений свойств одновременно. Эти методы используют PropertyValues класс, представляющий коллекцию свойств и их значений. PropertyValues можно получить для текущих или исходных значений, а также для значений, хранящихся в базе данных. Например:

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

Эти объекты PropertyValues не очень полезны самостоятельно. Однако их можно объединить для выполнения распространенных операций, необходимых при управлении сущностями. Это полезно при работе с объектами передачи данных и при разрешении конфликтов оптимистического параллелизма. В следующих разделах показаны некоторые примеры.

Задание текущих или исходных значений из сущности или DTO

Текущие или исходные значения сущности можно обновить путем копирования значений из другого объекта. Например, рассмотрим BlogDto объект передачи данных (DTO) с теми же свойствами, что и тип сущности:

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

Это можно использовать для задания текущих значений отслеживаемой сущности с помощью PropertyValues.SetValues:

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

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

Этот метод иногда используется при обновлении сущности со значениями, полученными из вызова службы или клиента в n-уровневом приложении. Обратите внимание, что используемый объект не должен иметь тот же тип, что и сущность, если она имеет свойства, имена которых соответствуют именам сущности. В приведенном выше примере экземпляр DTO BlogDto используется для задания текущих значений отслеживаемой сущности Blog .

Обратите внимание, что свойства будут помечены только как измененные, если набор значений отличается от текущего значения.

Задание текущих или исходных значений из словаря

В предыдущем примере задаются значения из экземпляра сущности или DTO. Такое же поведение доступно, если значения свойств хранятся в виде пар "имя-значение" в словаре. Например:

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

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

Задание текущих или исходных значений из базы данных

Текущие или исходные значения сущности можно обновить с помощью последних значений из базы данных путем вызова GetDatabaseValues() или GetDatabaseValuesAsync использования возвращаемого объекта для задания текущих или исходных значений или обоих. Например:

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

Создание клонированного объекта, содержащего текущие, исходные или значения базы данных

Объект PropertyValues, возвращаемый из CurrentValues, OriginalValues или GetDatabaseValues, можно использовать для создания клона сущности с помощью PropertyValues.ToObject(). Например:

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

Обратите внимание, что ToObject возвращает новый экземпляр, который не отслеживается DbContext. Возвращаемый объект также не имеет связей, установленных для других сущностей.

Клонируемый объект может быть полезен для устранения проблем, связанных с параллельными обновлениями базы данных, особенно при привязке данных к объектам определенного типа. Дополнительные сведения см . в разделе "Оптимистическое параллелизм ".

Работа со всеми навигациями сущности

EntityEntry.Navigations возвращает значение IEnumerable<T>NavigationEntry для каждой навигации сущности. EntityEntry.References и EntityEntry.Collections сделайте то же самое, но ограничено ссылочными или коллекциями навигаций соответственно. Это можно использовать для выполнения действия для каждой навигации сущности. Например, для принудительной загрузки всех связанных сущностей:

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

Работа со всеми элементами сущности

Обычные свойства и свойства навигации имеют разные состояния и поведение. Поэтому обычно обрабатываются навигации и не навигации отдельно, как показано в приведенных выше разделах. Однако иногда это может быть полезно, чтобы сделать что-то с любым членом сущности, независимо от того, является ли это регулярное свойство или навигация. EntityEntry.Member и EntityEntry.Members предоставляются для этой цели. Например:

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

Выполнение этого кода в блоге из примера создает следующие выходные данные:

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]

Совет

В представлении отладки средства отслеживания изменений отображаются такие сведения. Представление отладки для всего средства отслеживания изменений создается из отдельных EntityEntry.DebugView отслеживаемых сущностей.

Find и FindAsync

DbContext.Find, , DbContext.FindAsyncDbSet<TEntity>.Findи DbSet<TEntity>.FindAsync предназначены для эффективного поиска одной сущности, когда его первичный ключ известен. Найдите первые проверка, если сущность уже отслеживается, и если это так, возвращает сущность немедленно. Запрос базы данных выполняется только в том случае, если сущность не отслеживается локально. Например, рассмотрим этот код, который вызывает поиск дважды для одной сущности:

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

Выходные данные этого кода (включая ведение журнала EF Core) при использовании 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.

Обратите внимание, что первый вызов не находит сущность локально и поэтому выполняет запрос базы данных. И наоборот, второй вызов возвращает тот же экземпляр, не запрашивая базу данных, так как она уже отслеживается.

Поиск возвращает значение NULL, если сущность с заданным ключом не отслеживается локально и не существует в базе данных.

Составные ключи

Поиск также можно использовать с составными ключами. Например, рассмотрим OrderLine сущность с составным ключом, состоящим из идентификатора заказа и идентификатора продукта:

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

    //...
}

Составной ключ необходимо настроить для DbContext.OnModelCreating определения ключевых частей и их порядка. Например:

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

Обратите внимание, что OrderId первая часть ключа и ProductId является второй частью ключа. Этот порядок должен использоваться при передаче значений ключей в Find. Например:

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

Использование ChangeTracker.Entries для доступа ко всем отслеживаемых сущностям

До сих пор мы получили доступ только к одному за раз EntityEntry . ChangeTracker.Entries() возвращает EntityEntry для каждой сущности, отслеживаемой dbContext. Например:

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

Этот код создаст следующие выходные данные:

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

Обратите внимание, что возвращаются записи для блогов и записей. Результаты можно отфильтровать по определенному типу сущности с помощью универсальной 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}");
}

Выходные данные этого кода показывают, что возвращаются только записи:

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

Кроме того, использование универсальной перегрузки возвращает универсальные EntityEntry<TEntity> экземпляры. Это то, что позволяет свободному доступу к свойству Id в этом примере.

Универсальный тип, используемый для фильтрации, не должен быть сопоставленным типом сущности; Вместо этого можно использовать несопоставленный базовый тип или интерфейс. Например, если все типы сущностей в модели реализуют интерфейс, определяющий их ключевое свойство:

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

Затем этот интерфейс можно использовать для работы с ключом любой отслеживаемой сущности строго типизированным образом. Например:

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

Использование DbSet.Local для запроса отслеживаемых сущностей

Запросы EF Core всегда выполняются в базе данных и возвращаются только сущности, сохраненные в базе данных. DbSet<TEntity>.Local предоставляет механизм запроса DbContext для локальных отслеживаемых сущностей.

Так как DbSet.Local используется для запроса отслеживаемых сущностей, обычно загружает сущности в DbContext, а затем работает с загруженными сущностями. Это особенно верно для привязки данных, но также может быть полезно в других ситуациях. Например, в следующем коде база данных сначала запрашивается для всех блогов и записей. Load Метод расширения используется для выполнения этого запроса с результатами, отслеживаемыми контекстом, без возврата непосредственно в приложение. (Использование ToList или аналогичное имеет тот же эффект, но с дополнительными затратами на создание возвращаемого списка, который не нужен здесь.) Затем этот пример используется DbSet.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}");
}

Обратите внимание, что, в отличие от ChangeTracker.Entries()экземпляров сущностей, DbSet.Local возвращается непосредственно. EntityEntry может, конечно, всегда быть получен для возвращаемой сущности путем вызова DbContext.Entry.

Локальное представление

DbSet<TEntity>.Local возвращает представление локально отслеживаемых сущностей, отражающих текущий момент EntityState этих сущностей. В частности, это означает, что:

  • Added сущности включены. Обратите внимание, что это не относится к обычным запросам EF Core, так как Added сущности еще не существуют в базе данных и поэтому никогда не возвращаются запросом базы данных.
  • Deleted сущности исключаются. Обратите внимание, что это еще раз не относится к обычным запросам EF Core, так как Deleted сущности по-прежнему существуют в базе данных и поэтому возвращаются запросами базы данных.

Все это означает, что DbSet.Local представление данных, отражающих текущее концептуальное состояние графа сущностей, с Added сущностями, включенными и Deleted сущностями, исключенными. Это соответствует состоянию базы данных после вызова SaveChanges.

Обычно это идеальное представление для привязки данных, так как оно представляет пользователю данные, как они понимают его на основе изменений, внесенных приложением.

Следующий код демонстрирует это, помечая одну запись, а Deleted затем добавляя новую запись, помечая ее как 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}");
}

Выходные данные из этого кода:

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

Обратите внимание, что удаленная запись удаляется из локального представления, а добавленная запись включена.

Использование local для добавления и удаления сущностей

DbSet<TEntity>.Local возвращает экземпляр LocalView<TEntity>. Это реализация ICollection<T> , которая создает и реагирует на уведомления при добавлении и удалении сущностей из коллекции. (Это та же концепция, что ObservableCollection<T>и проекция по существующим записям отслеживания изменений EF Core, а не как независимая коллекция.)

Уведомления локального представления подключены к отслеживанию изменений DbContext, чтобы локальное представление оставалось в синхронизации с DbContext. В частности:

  • Добавление новой сущности для DbSet.Local ее отслеживания с помощью DbContext обычно в Added состоянии. (Если сущность уже имеет созданное значение ключа, то она отслеживается как Unchanged вместо.)
  • Удаление сущности из DbSet.Local причин, помечает ее как Deleted.
  • Сущность, которая отслеживается DbContext, автоматически появится в DbSet.Local коллекции. Например, выполнение запроса для привлечения дополнительных сущностей автоматически приводит к обновлению локального представления.
  • Сущность, помеченная как Deleted будет удалена из локальной коллекции автоматически.

Это означает, что локальное представление можно использовать для управления отслеживаемыми сущностями, просто добавив и удалив из коллекции. Например, давайте изменим предыдущий пример кода, чтобы добавить и удалить записи из локальной коллекции:

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

Выходные данные остаются неизменными из предыдущего примера, так как изменения, внесенные в локальное представление, синхронизируются с DbContext.

Использование локального представления для привязки данных Windows Forms или WPF

DbSet<TEntity>.Local формирует основу привязки данных к сущностям EF Core. Однако windows Forms и WPF лучше всего работают при использовании с определенным типом уведомления о том, что они ожидают. Локальное представление поддерживает создание следующих типов коллекций:

Например:

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

Дополнительные сведения о привязке данных WPF с EF Core см. в статье "Начало работы с WPF" и "Начало работы с Windows Forms" для получения дополнительных сведений о привязке данных Windows Forms с EF Core.

Совет

Локальное представление для заданного экземпляра DbSet создается безумно при первом доступе, а затем кэшируется. Создание LocalView выполняется быстро и не использует значительную память. Однако он вызывает DetectChanges, который может быть медленным для большого количества сущностей. Коллекции, созданные ToObservableCollection и ToBindingList также создаются лениво, а затем кэшируются. Оба этих метода создают новые коллекции, которые могут быть медленными и использовать много памяти, когда участвуют тысячи сущностей.