Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Каждая инстанция DbContext отслеживает изменения, внесенные в сущности. Эти отслеживаемые сущности, в свою очередь, управляют изменениями базы данных при SaveChanges вызове. Это рассматривается в разделе "Отслеживание изменений" в EF Core, и в этом документе предполагается, что состояния сущностей и основы отслеживания изменений Entity Framework Core (EF Core) понятны.
Для отслеживания изменений свойств и связей требуется, чтобы DbContext мог обнаруживать эти изменения. В этом документе описывается, как это обнаружение происходит, а также как использовать уведомления о свойствах или прокси-серверы отслеживания изменений для принудительного обнаружения изменений.
Подсказка
Вы можете запускать и отлаживать весь код в этом документе, скачав пример кода из GitHub.
Отслеживание изменений мгновенных снимков
По умолчанию EF Core создает моментальный снимок значений свойств каждой сущности при первом отслеживании экземпляром DbContext. Затем значения, хранящиеся в этом моментальном снимке, сравниваются с текущими значениями сущности, чтобы определить, какие значения свойств изменились.
Это обнаружение изменений происходит при вызове SaveChanges, чтобы убедиться, что все измененные значения обнаружены перед отправкой обновлений в базу данных. Однако обнаружение изменений также происходит в другие моменты времени, чтобы гарантировать, что приложение работает с информацией об отслеживании даты up-to. Обнаружение изменений может быть осуществлено принудительно в любое время путем вызова ChangeTracker.DetectChanges().
Если требуется обнаружение изменений
Обнаружение изменений необходимо, если свойство или навигация были изменены без использования EF Core для внесения этого изменения. Например, попробуйте загрузить блоги и записи, а затем внести изменения в эти сущности:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Просмотр представления отладки отслеживания изменений перед вызовом ChangeTracker.DetectChanges() показывает, что внесенные изменения не обнаружены, поэтому не отражаются в состояниях сущности и измененных данных свойств:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog (Updated!)' Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, <not found>]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
В частности, состояние записи блога по-прежнему Unchanged
, и новая запись не отображается как отслеживаемая сущность. Проницательные замечают, что свойства сообщают о новых значениях, даже если эти изменения еще не обнаружены EF Core. Это происходит потому, что дебаггер считывает текущие значения прямо из объекта сущности.
Сравните это с представлением отладки после вызова DetectChanges:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
Blog: {Id: 1}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Теперь блог правильно помечен как Modified
, и новая запись обнаружена и отслеживается как Added
.
В начале этого раздела мы заявили, что обнаружение изменений необходимо при не использовании EF Core для внесения изменений. Это то, что происходит в приведенном выше коде. То есть изменения свойства и навигации вносятся непосредственно в экземпляры сущностей, а не с помощью методов EF Core.
Сравните это со следующим кодом, который изменяет сущности таким же образом, но на этот раз с помощью методов EF Core:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
context.Entry(blog).Property(e => e.Name).CurrentValue = ".NET Blog (Updated!)";
// Add a new entity to the DbContext
context.Add(
new Post
{
Blog = blog,
Title = "What’s next for System.Text.Json?",
Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
В этом случае представление отладки средства отслеживания изменений показывает, что известны все состояния сущности и изменения свойств, даже если обнаружение изменений не произошло. Это связано с тем, что PropertyEntry.CurrentValue это метод EF Core, который означает, что EF Core сразу же знает об изменениях, внесенных этим методом. Аналогичным образом вызов DbContext.Add позволяет EF Core немедленно знать о новой сущности и отслеживать ее соответствующим образом.
Подсказка
Не пытайтесь избежать обнаружения изменений, всегда используя методы EF Core для изменения сущностей. Это часто является более громоздким и работает менее хорошо, чем внесение изменений в сущности обычным образом. Цель этого документа — сообщить о том, когда необходимо выявлять изменения и когда это не требуется. Цель заключается в том, чтобы не поощрять предотвращение обнаружения изменений.
Методы, которые автоматически обнаруживают изменения
DetectChanges() вызывается автоматически методами, в которых это может повлиять на результаты. Этими методами являются:
- DbContext.SaveChanges и DbContext.SaveChangesAsync, чтобы убедиться, что все изменения обнаружены перед обновлением базы данных.
- ChangeTracker.Entries() и ChangeTracker.Entries<TEntity>(), чтобы гарантировать актуальность состояния сущности и измененных свойств up-to-date.
- ChangeTracker.HasChanges(), чтобы убедиться, что результат является точным.
- ChangeTracker.CascadeChanges(), чтобы обеспечить правильные состояния сущностей для основных или родительских сущностей перед каскадированием.
- DbSet<TEntity>.Local, чтобы убедиться, что отслеживаемый граф up-to-date.
Есть также некоторые места, где обнаружение изменений происходит только в одном экземпляре сущности, а не на всем графе отслеживаемых сущностей. Ниже перечислены следующие места:
- При использовании DbContext.Entry, убедитесь, что состояние и измененные свойства сущности актуальны на дату up-to.
- При использовании EntityEntry таких методов, как
Property
,Collection
Reference
илиMember
для обеспечения изменений свойств, текущих значений и т. д. up-to-date. - При удалении зависимой или дочерней сущности, которое произойдет из-за разрыва необходимой связи. Это определяет, когда сущность не должна быть удалена, если она была перепривязана к другому родителю.
Локальное обнаружение изменений для одной сущности можно активировать явным образом путем вызова EntityEntry.DetectChanges().
Замечание
Локальное обнаружение изменений может упустить некоторые изменения, которые будут выявлены при полном обнаружении. Это происходит, когда каскадные действия, возникающие из-за незамеченных изменений в других сущностях, оказывают влияние на рассматриваемую сущность. В таких ситуациях приложению может потребоваться принудительное полное сканирование всех сущностей путем явного вызова ChangeTracker.DetectChanges().
Отключение автоматического обнаружения изменений
Производительность обнаружения изменений не является узким местом для большинства приложений. Однако обнаружение изменений может стать проблемой производительности для некоторых приложений, отслеживающих тысячи сущностей. (Точное число будет зависеть от многих вещей, таких как количество свойств в сущности.) По этой причине автоматическое обнаружение изменений может быть отключено с помощью ChangeTracker.AutoDetectChangesEnabled. Например, рассмотрите возможность обработки сущностей связывания в зависимости "многие ко многим" с данными полезной нагрузки.
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entityEntry in ChangeTracker.Entries<PostTag>()) // Detects changes automatically
{
if (entityEntry.State == EntityState.Added)
{
entityEntry.Entity.TaggedBy = "ajcvickers";
entityEntry.Entity.TaggedOn = DateTime.Now;
}
}
try
{
ChangeTracker.AutoDetectChangesEnabled = false;
return await base.SaveChangesAsync(cancellationToken); // Avoid automatically detecting changes again here
}
finally
{
ChangeTracker.AutoDetectChangesEnabled = true;
}
}
Как мы знаем из предыдущего раздела, и ChangeTracker.Entries<TEntity>(), и DbContext.SaveChanges автоматически обнаруживают изменения. Однако, после вызова вхождений, код не вносит никаких изменений в состояние сущностей или свойств. (Установка обычных значений свойств в добавленных сущностях не приводит к изменениям состояния.) Поэтому код отключает ненужное автоматическое обнаружение изменений при вызове в базовый метод SaveChanges. Код также использует блок try/finally, чтобы убедиться, что параметр по умолчанию восстановлен, даже если SaveChanges завершается сбоем.
Подсказка
Не предполагайте, что код должен отключить автоматическое обнаружение изменений для правильного выполнения. Это необходимо только в том случае, если при профилировании приложения, отслеживающего множество сущностей, выясняется, что производительность обнаружения изменений вызывает проблемы.
Обнаружение изменений и преобразований значений
Чтобы использовать отслеживание изменений с моментальными снимками для типа сущности, EF Core должен иметь возможность:
- Сделайте снимок каждого значения свойства при отслеживании сущности
- Сравните это значение с текущим значением свойства
- Создание хэш-кода для значения
Это выполняется автоматически в EF Core для типов, которые можно напрямую сопоставить с базой данных. Однако, когда преобразователь значений используется для сопоставления свойства, этот преобразователь должен указать, как выполнять эти действия. Это достигается с помощью средства сравнения значений и подробно описано в документации по сравнениям значений .
Сущности уведомлений
Отслеживание изменений моментальных снимков рекомендуется для большинства приложений. Однако приложения, которые отслеживают множество сущностей и /или вносят много изменений в эти сущности, могут воспользоваться реализацией сущностей, которые автоматически уведомляют EF Core при изменении значений свойств и навигации. Они называются "сущностями уведомлений".
Внедрение объектов уведомлений
Сущности уведомлений используют интерфейсы INotifyPropertyChanging и INotifyPropertyChanged, которые являются частью библиотеки базовых классов .NET (BCL). Эти интерфейсы определяют события, которые должны быть запущены до и после изменения значения свойства. Рассмотрим пример.
public class Blog : INotifyPropertyChanging, INotifyPropertyChanged
{
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
public int Id
{
get => _id;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Id)));
_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
}
}
private string _name;
public string Name
{
get => _name;
set
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Name)));
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
Кроме того, все навигации по коллекции должны реализовываться INotifyCollectionChanged
; в приведенном выше примере это удовлетворено с помощью ObservableCollection<T> записей. EF Core также поставляется с ObservableHashSet<T> реализацией, которая имеет более эффективные поиски за счет стабильного упорядочения.
Большая часть этого кода уведомлений обычно перемещается в несопоставленный базовый класс. Рассмотрим пример.
public class Blog : NotifyingEntity
{
private int _id;
public int Id
{
get => _id;
set => SetWithNotify(value, out _id);
}
private string _name;
public string Name
{
get => _name;
set => SetWithNotify(value, out _name);
}
public IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public abstract class NotifyingEntity : INotifyPropertyChanging, INotifyPropertyChanged
{
protected void SetWithNotify<T>(T value, out T field, [CallerMemberName] string propertyName = "")
{
NotifyChanging(propertyName);
field = value;
NotifyChanged(propertyName);
}
public event PropertyChangingEventHandler PropertyChanging;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private void NotifyChanging(string propertyName)
=> PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}
Настройка объектов уведомлений
EF Core не может проверить, что INotifyPropertyChanging
или INotifyPropertyChanged
полностью реализованы для использования с EF Core. В частности, некоторые из этих интерфейсов работают с уведомлениями только по определенным свойствам, а не по всем свойствам (включая навигации), как того требует EF Core. По этой причине EF Core не автоматически подключается к этим событиям.
Вместо этого EF Core необходимо настроить для использования этих сущностей уведомлений. Обычно это делается для всех типов сущностей путем вызова ModelBuilder.HasChangeTrackingStrategy. Рассмотрим пример.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications);
}
(Стратегия также может быть задана по-разному для различных типов сущностей с использованием EntityTypeBuilder.HasChangeTrackingStrategy, но это обычно контрпродуктивно, так как для тех типов, которые не являются уведомляемыми сущностями, все равно требуется DetectChanges.)
Для полного отслеживания изменений уведомлений требуется, чтобы как INotifyPropertyChanging
, так и INotifyPropertyChanged
были реализованы. Это позволяет сохранять исходные значения непосредственно перед изменением значения свойства, избегая необходимости для EF Core создавать снимок состояния при отслеживании сущности. Типы сущностей, реализующие только INotifyPropertyChanged
, также могут быть использованы с EF Core. В этом случае EF по-прежнему создает моментальный снимок при отслеживании сущности, чтобы фиксировать исходные значения, но затем использует уведомления для мгновенного обнаружения изменений, вместо необходимости вызова DetectChanges.
Различные значения ChangeTrackingStrategy представлены в следующей таблице.
Стратегия отслеживания изменений | Необходимые интерфейсы | Требуется DetectChanges | Исходные значения моментальных снимков |
---|---|---|---|
Снимок | Отсутствует | Да | Да |
Измененные Уведомления | INotifyPropertyChanged (интерфейс для уведомления об изменении свойства) | нет | Да |
ИзменяющиесяИИзменённыеУведомления | INotifyPropertyChanged и INotifyPropertyChanging | нет | нет |
УведомленияОбИзмененияхИИзмененныхСоЗначениямиПоУмолчанию | INotifyPropertyChanged и INotifyPropertyChanging | нет | Да |
Использование сущностей уведомлений
Сущности уведомлений ведут себя как любые другие сущности, за исключением того, что для внесения изменений в экземпляры этих сущностей не требуется вызов ChangeTracker.DetectChanges() для обнаружения изменений. Рассмотрим пример.
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
В случае обычных сущностей представление отладки отслеживания изменений показало, что изменения не обнаруживаются, пока не был вызван DetectChanges. Просмотр представления отладки при использовании сущностей уведомлений показывает, что эти изменения были обнаружены немедленно:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482643}]
Post {Id: -2147482643} Added
Id: -2147482643 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
Blog: {Id: 1}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
Прокси-серверы отслеживания изменений
EF Core может динамически создавать типы прокси-серверов, реализующие INotifyPropertyChanging и INotifyPropertyChanged. Для этого требуется установить пакет NuGet Microsoft.EntityFrameworkCore.Proxies и включить прокси для отслеживания изменений, UseChangeTrackingProxies например:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseChangeTrackingProxies();
Создание динамического прокси-сервера включает создание нового динамического типа .NET (с помощью реализации прокси-серверов Castle.Core ), который наследует от типа сущности, а затем переопределяет все методы задания свойств. Таким образом, типы сущностей для прокси-серверов должны быть типами, которые могут быть унаследованы от и должны иметь свойства, которые можно переопределить. Кроме того, навигации коллекции, созданные явным образом, должны реализовать INotifyCollectionChanged :
public class Blog
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Post> Posts { get; } = new ObservableCollection<Post>();
}
public class Post
{
public virtual int Id { get; set; }
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual int BlogId { get; set; }
public virtual Blog Blog { get; set; }
}
Одним из существенных недостатков прокси-серверов отслеживания изменений является то, что EF Core всегда должен отслеживать экземпляры прокси-сервера, никогда не экземпляры базового типа сущности. Это связано с тем, что экземпляры базового типа сущности не будут создавать уведомления, что означает, что изменения, внесенные в эти сущности, будут пропущены.
EF Core автоматически создает экземпляры прокси при выполнении запроса к базе данных, поэтому этот недостаток обычно ограничивается отслеживанием новых экземпляров сущностей. Эти экземпляры должны создаваться с помощью CreateProxy методов расширения, а не в обычном режиме, используя new
. Это означает, что код из предыдущих примеров теперь должен использовать CreateProxy
:
using var context = new BlogsContext();
var blog = await context.Blogs.Include(e => e.Posts).FirstAsync(e => e.Name == ".NET Blog");
// Change a property value
blog.Name = ".NET Blog (Updated!)";
// Add a new entity to a navigation
blog.Posts.Add(
context.CreateProxy<Post>(
p =>
{
p.Title = "What’s next for System.Text.Json?";
p.Content = ".NET 5.0 was released recently and has come with many...";
}));
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
События отслеживания изменений
EF Core запускает событие ChangeTracker.Tracked при первом отслеживании сущности. Будущие изменения состояния сущности приводят к событиям ChangeTracker.StateChanged . Дополнительные сведения см. в разделе событий .NET в EF Core .
Замечание
Событие StateChanged
не запускается, когда сущность начинает отслеживаться, даже если состояние изменилось с Detached
на одно из других состояний. Убедитесь, что вы прослушиваете оба события StateChanged
и Tracked
, чтобы получить все соответствующие уведомления.