Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Существует четыре основных API для доступа к сущностям, отслеживаемым по DbContext:
- DbContext.Entry возвращает EntityEntry<TEntity> экземпляр для данного экземпляра сущности.
- ChangeTracker.Entries возвращает EntityEntry<TEntity> экземпляры для всех отслеживаемых сущностей или для всех отслеживаемых сущностей заданного типа.
- DbContext.Find, DbContext.FindAsync, DbSet<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> приводит к тому, что EntityEntry<TEntity> предоставляет доступ к этой информации для этой сущности. Рассмотрим пример.
using var context = new BlogsContext();
var blog = await context.Blogs.SingleAsync(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 | Описание |
|---|---|
| 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";
Оба метода Property, использованные выше, возвращают строго типизированный экземпляр универсального типа PropertyEntry<TEntity,TProperty>. Использование этого универсального типа предпочтительнее, так как оно позволяет получить доступ к значениям свойств без упаковки типов значений. Однако если тип сущности или свойства не известен во время компиляции, вместо этого можно получить не универсальный PropertyEntry код:
PropertyEntry propertyEntry = context.Entry(blog).Property("Name");
Это позволяет получить доступ к информации о свойствах любого объекта независимо от его типа, но с затратами на преобразование типов значений. Рассмотрим пример.
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";
В следующей таблице приведены сведения о свойстве, предоставляемые PropertyEntry:
| Член PropertyEntry | Описание |
|---|---|
| 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>.Collection и EntityEntry.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 | Описание |
|---|---|
| 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 = await context.Entry(blog).GetDatabaseValuesAsync();
Эти объекты 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 = await context.Entry(blog).GetDatabaseValuesAsync();
context.Entry(blog).CurrentValues.SetValues(databaseValues);
context.Entry(blog).OriginalValues.SetValues(databaseValues);
Создание клонированного объекта, содержащего текущие, исходные или значения базы данных
Объект PropertyValues, возвращаемый из CurrentValues, OriginalValues или GetDatabaseValues, можно использовать для создания клона сущности с помощью PropertyValues.ToObject(). Рассмотрим пример.
var clonedBlog = (await context.Entry(blog).GetDatabaseValuesAsync()).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 предназначены для эффективного поиска одной сущности, когда его первичный ключ известен. Find сначала проверяет, отслеживается ли сущность, и если да, немедленно возвращает эту сущность. Запрос базы данных выполняется только в том случае, если сущность не отслеживается локально. Например, рассмотрим этот код, который вызывает поиск дважды для одной сущности:
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.");
Выходные данные этого кода (включая ведение журнала 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 = await context.OrderLines.FindAsync(orderId, productId);
Использование ChangeTracker.Entries для доступа ко всем отслеживаемых сущностям
До сих пор мы получали доступ только к одному EntityEntry за раз. ChangeTracker.Entries() возвращает EntityEntry для каждой сущности, отслеживаемой DbContext. Рассмотрим пример.
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}");
}
Этот код создаст следующие выходные данные:
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();
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}");
}
Обратите внимание, что, в отличие от 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 = 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}");
}
Выходные данные из этого кода:
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 = 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}");
}
Выходные данные остаются неизменными из предыдущего примера, так как изменения, внесенные в локальное представление, синхронизируются с DbContext.
Использование локального представления для привязки данных Windows Forms или WPF
DbSet<TEntity>.Local формирует основу привязки данных к сущностям EF Core. Однако Windows Forms и WPF работают лучше всего, когда используются с тем типом коллекции уведомлений, который от них ожидается. Локальное представление поддерживает создание следующих типов коллекций:
- LocalView<TEntity>.ToObservableCollection() возвращает элемент ObservableCollection<T>, необходимый для привязки данных WPF.
- LocalView<TEntity>.ToBindingList() возвращает BindingList<T> для привязки данных Windows Forms.
Рассмотрим пример.
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 также создаются лениво, а затем кэшируются. Оба этих метода создают новые коллекции, которые могут быть медленными и использовать много памяти, когда участвуют тысячи сущностей.