Как entity framework отслеживает изменения

Обновлено: 07.07.2024

Обновление существующих объектов в базе данных реализуется также просто, как и вставка данных. Для этого нужно сначала извлечь нужный объект из базы данных, затем изменить одно или несколько значений его свойств, а затем сохранить изменения в базе данных используя метод DbContext.SaveChanges().

Обновление одного объекта

Допустим в таблице Customers существуют покупатели с фамилиями “Иванов”. Мы хотим поменять фамилии этих покупателей на “Сидоров”. Сделать это можно с помощью следующего кода:

Этот пример использует запрос LINQ для загрузки первого попавшегося покупателя с фамилией Иванов. Затем мы изменяем фамилию пользователя и вызываем метод SaveChanges(), при котором Entity Framework определяет, что объект сущности изменился и его нужно обновить используя следующий SQL-запрос:

Как описывалось в предыдущей статье, для указания на изменение данных можно использовать модель состояний Entity Framework. Состояние сущностного объекта указывается через свойство State класса DbEntityEntry, который в свою очередь доступен через свойство DbSet.Entry. Для указания на то, что объект изменился используется значение EntityState.Modified. Ниже показан пример, как мы можем обновить фамилии всех Ивановых в таблице, а не только первого попавшегося:

Этот код сгенерирует по одной инструкции UPDATE для каждого найденного покупателя с фамилией Иванов. Стоит отметить, что в показанных выше запросах мы загружали все данные пользователя, а во втором примере при использовании состояний, мы обновляли все данные, хотя достаточно лишь обновить только фамилию. (При автоматическом обновлении свойств модели, как в первом примере, Entity Framework будет обновлять только столбец LastName, как показано в сгенерированном SQL-коде). Класс DbEntityEntry содержит метод Property(), в котором можно явно указать свойство модели, которое нужно обновлять. Ниже показан более оптимизированный запрос для смены фамилий всех пользователей, в котором мы загружаем только их идентификаторы и обновляем только фамилии:

Метод Property() возвращает объект DbPropertyEntry, в свойстве IsModified которого мы указываем, что состояние объекта изменилось. Это аналогично использованию свойства State класса DbEntityEntry. Обратите внимание на использование метода DbSet.Attach() при перечислении. Этот метод указывает, что объект нужно явно прикрепить в сущностной модели данных, т.к. он был создан вручную, а не загружен из базы данных. Действительно, следующий запрос LINQ to Entities:

Обновление связанных объектов

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

Давайте рассмотрим обновление связанных объектов в контексте нашего примера. Допустим мы допустили ошибку при определении заказа с идентификатором OrderId = 3, и нам нужно переместить этот заказ к другому пользователю (в моей тестовой базе данных этот заказ принадлежит покупателю с CustomerId = 4, у вас соответственно это может отличаться). Ниже показан пример, как это можно сделать с использованием навигационного свойства:

В этом примере мы сначала выбираем покупателя, кому будет принадлежать новый заказ, затем выбираем сам заказ, после чего обновляем ссылку на покупателя через навигационное свойство Order.Customer. Это говорит Entity Framework о том, что нужно сгенерировать инструкцию UPDATE для обновления соответствующей записи в таблице Orders:

Entity Framework автоматически ищет измененные свойства в прикрепленных сущностных объектах и обновляет только их, как говорилось в предыдущем разделе. В данном примере обновляется внешний ключ UserId, который мы явно определили в предыдущей статье.

Стоит отметить, что в этом примере мы использовали навигационное свойство на стороне зависимой таблицы Orders. Можно обновить связанные объекты также использовав навигационное свойство Customer.Orders на стороне базовой таблицы, но для этого нужно обязательно при выборке данных покупателя включить и выборку связанных с ним заказов с помощью прямой загрузки, чтобы Entity Framework мог отслеживать изменения в коллекции заказов пользователя:

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

Мы можем еще более оптимизировать первый пример, используя в запросах загрузку только идентификатора пользователя, идентификатора и внешнего ключа заказа, чтобы не выбирать слишком много данных. Как говорилось в предыдущем разделе, такие выбранные данные нужно будет явно прикрепить к контексту используя метод DbSet.Attach(). Ниже показан соответствующий пример:

Сгенерированный SQL-оператор UPDATE для этого примера не отличается от того, который мы приводили выше, но при этом значительно увеличивается производительность двух операторов SELECT для выборки только ключей.

Универсальный метод обновления

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

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

Каждый экземпляр DbContext отслеживает изменения, внесенные в сущности. Эти отслеживание сущностей, в свою очередь, записывают изменения в базу данных при вызове метода SaveChanges . Это рассматривается в Отслеживание изменений в EF Core, и в этом документе предполагается, что состояния сущностей и основы отслеживания изменений Entity Framework Core (EF Core) понятны.

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

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

Отслеживание изменений по моментальному снимку

По умолчанию EF Core создает моментальный снимок значений свойств каждой сущности при ее первой отслеживании экземпляром DbContext. Значения, хранящиеся в этом снимке, затем сравниваются с текущими значениями сущности, чтобы определить, какие значения свойств изменились.

Это обнаружение изменений происходит при вызове SaveChanges, чтобы гарантировать, что все измененные значения будут обнаружены перед отправкой обновлений в базу данных. Однако обнаружение изменений также происходит в других случаях, чтобы обеспечить работу приложения с актуальными данными отслеживания. Обнаружение изменений можно выполнить принудительно в любое время, вызвав ChangeTracker. DetectChanges ().

Когда требуется обнаружение изменений

Обнаружение изменений необходимо при изменении свойства или навигации без использования EF Core для внесения этого изменения. Например, можно загрузить блоги и записи, а затем внести изменения в эти сущности:

Просмотр представления отладки средства записи изменений перед вызовом ChangeTracker. DetectChanges () показывает, что внесенные изменения не были обнаружены и, следовательно, не отражаются в состояниях сущностей и измененных данных свойств:

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

Сравните это с представлением отладки после вызова DetectChanges:

Теперь блог правильно помечен как Modified , и обнаружена новая запись, которая будет записана как Added .

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

Сравните это с приведенным ниже кодом, который изменяет сущности таким же образом, но на этот раз с помощью EF Core методов:

В этом случае представление отладка изменений Tracker показывает, что все состояния сущностей и изменения свойств известны, даже если обнаружение изменений не произошло. Это обусловлено тем, что PropertyEntry. куррентвалуе является методом EF Core, что означает, что EF Core немедленно знает об изменениях, внесенных этим методом. Аналогичным образом, вызов DbContext. Add позволяет EF Core немедленно узнать о новой сущности и отвести ее соответствующим образом.

Не пытайтесь обнаружить изменения, всегда используя методы EF Core для внесения изменений в сущности. Это часто бывает более громоздким и менее хорошо, чем внесение изменений в сущности обычным способом. Цель этого документа — сообщить о том, когда нужно обнаружить изменения, а когда — нет. Намерение не порекомендую избегать обнаружения изменений.

Методы, которые автоматически обнаруживают изменения

DetectChanges () вызывается автоматически методами, где это может повлиять на результаты. Этими методами являются:

    и DbContext. SaveChangesAsync, чтобы убедиться, что все изменения обнаружены перед обновлением базы данных. и ChangeTracker. States (), чтобы обеспечить актуальность состояний сущностей и измененных свойств. , чтобы убедиться, что результат является точным. , чтобы обеспечить правильное состояние сущности для сущностей Principal или Parent перед каскадным. Домик > . Local, чтобы обеспечить актуальность отслеживающего графа.

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

  • При использовании DbContext. Entry, чтобы обеспечить актуальность состояния и измененных свойств сущности.
  • При использовании методов ентитентри , таких как , или, Collection Reference Member чтобы обеспечить актуальность изменений свойств, текущих значений и т. д.
  • Если зависимая или Дочерняя сущность будет удалена из-за того, что необходимая связь была разорвана. Это обнаруживает, что сущность не должна удаляться, так как она была повторно дочерней.

Локальное обнаружение изменений для одной сущности может быть инициировано явным образом путем вызова ентитентри. DetectChanges ().

Локальное обнаружение изменений может пропускать некоторые изменения, обнаруженные в результате полного обнаружения. Это происходит, когда каскадные действия, являющиеся результатом необнаруженных изменений в других сущностях, оказывают влияние на рассматриваемую сущность. В таких ситуациях приложению может потребоваться принудительно выполнить полную проверку всех сущностей путем явного вызова ChangeTracker. DetectChanges ().

Отключение автоматического обнаружения изменений

Производительность обнаружения изменений не является узким местом для большинства приложений. Однако обнаружение изменений может стать проблемой с производительностью для некоторых приложений, которые отслеживают тысячи сущностей. (Точное число зависит от многих вещей, например от количества свойств в сущности.) По этой причине автоматическое обнаружение изменений можно отключить с помощью ChangeTracker. аутодетектчанжесенаблед. Например, рассмотрим обработку сущностей JOIN в связи «многие ко многим» с полезной нагрузкой.

Как мы узнали из предыдущего раздела, оба элемента ChangeTracker. Sections > () и > автоматически обнаруживают изменения. Однако после вызова записей код не вносит изменения в состояние сущности или свойства. (Установка обычных значений свойств для добавленных сущностей не приводит к изменениям состояния.) Таким образом, код отключает ненужное автоматическое обнаружение изменений при вызове базового метода SaveChanges. Код также использует блок try/finally, чтобы гарантировать восстановление значения по умолчанию, даже если команда SaveChanges завершается неудачно.

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

Обнаружение изменений и преобразований значений

Чтобы использовать отслеживание изменений моментальных снимков с типом сущности, EF Core должен иметь возможность:

  • Создание моментального снимка каждого значения свойства при отслеживании сущности
  • Сравните это значение с текущим значением свойства
  • Создание хэш-кода для значения

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

Сущности уведомления

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

Реализация сущностей уведомления

Posts < get; >= new ObservableCollection

Кроме того, все переходы по коллекциям должны реализовываться INotifyCollectionChanged ; в приведенном выше примере это выполняется с помощью записей INotifyCollectionChanged . EF Core также поставляется с реализацией обсерваблехашсет T > , которая имеет более эффективные Просмотры за счет стабильного упорядочения.

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

_id; set => SetWithNotify(value, out _id); > private string _name; public string Name < get =>_name; set => SetWithNotify(value, out _name); > public IList

Posts < get; >= new ObservableCollection

(); > public abstract class NotifyingEntity : INotifyPropertyChanging, INotifyPropertyChanged < protected void SetWithNotify (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. хасчанжетраккингстратеги. Пример:

(Стратегия также может быть задана по-разному для разных типов сущностей с помощью ентититипебуилдер. хасчанжетраккингстратеги, но обычно это понизит производительность, так как DetectChanges все еще требуется для тех типов, которые не являются сущностями уведомлений.)

Для отслеживания изменений полного уведомления необходимо, INotifyPropertyChanging чтобы INotifyPropertyChanged были реализованы и, и. Это позволяет сохранять исходные значения непосредственно перед изменением значения свойства, избегая необходимости EF Core создавать моментальные снимки при отслеживании сущности. Типы сущностей, реализующие только реализацию, INotifyPropertyChanged можно также использовать с EF Core. В этом случае EF по-прежнему создает моментальный снимок при отслеживании сущности для отслеживания исходных значений, а затем использует уведомления для немедленного обнаружения изменений, а не требует вызова DetectChanges.

В следующей таблице приведены различные значения чанжетраккингстратеги .

чанжетраккингстратеги Необходимые интерфейсы Требуется DetectChanges Исходные значения моментальных снимков
Моментальный снимок Нет Да Да
чанжеднотификатионс INotifyPropertyChanged Нет Да
чангингандчанжеднотификатионс INotifyPropertyChanged и Инотифипропертичангинг Нет Нет
чангингандчанжеднотификатионсвисоригиналвалуес INotifyPropertyChanged и Инотифипропертичангинг Нет Да

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

Сущности уведомлений ведут себя так же, как и любые другие сущности, за исключением того, что внесение изменений в экземпляры сущности не требует вызова ChangeTracker. DetectChanges () для обнаружения этих изменений. Пример:

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

Прокси-серверы отслеживания изменений

Прокси-серверы отслеживания изменений были введены в EF Core 5,0.

EF Core могут динамически создавать типы прокси, реализующие инотифипропертичангинг и INotifyPropertyChanged. для этого необходимо установить пакет Microsoft. EntityFrameworkCore. прокси NuGet и включить прокси-серверы отслеживания изменений с помощью усечанжетраккингпроксиес , например:

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

EF Core создает экземпляры прокси-сервера автоматически при запросе к базе данных, поэтому эта негативная сторона обычно ограничена отслеживанием новых экземпляров сущностей. Эти экземпляры должны быть созданы с помощью методов расширения креатепрокси , а не обычным способом использования . Это означает, что теперь код из предыдущих примеров должен использовать CreateProxy :

e.Posts).First(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

События отслеживания изменений

StateChanged Событие не запускается при первой отслеживании сущности, несмотря на то, что состояние изменилось с Detached на одно из других состояний. Убедитесь, что прослушиваете StateChanged события и, Tracked чтобы получить все соответствующие уведомления.

Каждый экземпляр DbContext отслеживает изменения, внесенные в сущности. Эти отслеживаемые сущности, в свою очередь, записывают изменения в базу данных при вызове SaveChanges.

В этом документе представлены общие сведения об отслеживании изменений Entity Framework Core (EF Core) и о том, как это связано с запросами и обновлениями.

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

Для простоты в этой документации используются и описываются синхронные методы, такие как SaveChanges, а не их асинхронные эквиваленты, такие как SaveChangesAsync. Вызов и ожидание асинхронного метода можно заменить, если не указано иное.

Как отслеживать сущности

Экземпляры сущностей отслеживаются, если они:

  • получены из запросов к базе данных;
  • явно присоединены к DbContext с помощью Add , Attach , Update или аналогичных методов;
  • обнаружены как новые сущности, подключенные к существующим отслеживаемым сущностям.

Экземпляры сущностей больше не отслеживаются, если:

  • экземпляр DbContext удален;
  • средство отслеживания изменений очищено (EF Core 5.0 и более поздних версий);
  • сущности явно окончательно удалены.

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

  1. Создание экземпляра DbContext.
  2. Отслеживание определенных сущностей.
  3. Внесение определенных изменений в сущности.
  4. Вызов метода SaveChanges для обновления базы данных.
  5. Удаление экземпляра DbContext.

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

Состояния сущностей

Каждая сущность связана с заданным состоянием EntityState:

  • сущности Detached не отслеживаются Detached .
  • Сущности Added являются новыми и еще не вставлены в базу данных. Это означает, что они будут вставлены при вызове SaveChanges.
  • Сущности Unchanged Unchanged изменены, так как они были запрошены из базы данных. Все сущности, получаемые из запросов, изначально находятся в этом состоянии.
  • Сущности Modified были изменены с момента запроса из базы данных. Это означает, что они будут обновлены при вызове SaveChanges.
  • Сущности Deleted существуют в базе данных, но отмечены для удаления при вызове SaveChanges.

EF Core отслеживает изменения на уровне свойств. Например, если изменяется только одно значение свойства, обновление базы данных изменит только это значение. При этом свойства помечаются как измененные, только если сущность находится в состоянии Modified. (С другой стороны, состояние Modified означает, что хотя бы одно значение свойства помечено как измененное.)

В следующей таблице описаны различия между состояниями.

Состояние сущностей Отслеживается DbContext Существует в базе данных Измененные свойства Действие при вызове SaveChanges
Detached Нет - - -
Added Да Нет - Вставить
Unchanged Да Да Нет -
Modified Да Да Да Update
Deleted Да Да - Удалить

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

Отслеживание из запросов

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

Этот подход имеет несколько преимуществ по сравнению с явным отслеживанием экземпляров сущностей:

  • Он является простым. Состояниями сущностей редко требуется управлять явным образом — EF Core следит за изменениями состояния.
  • Обновления ограничиваются только теми значениями, которые действительно изменились.
  • Значения теневых свойств сохраняются и используются по мере необходимости. Это особенно важно, если внешние ключи хранятся в теневом состоянии.
  • Исходные значения свойств сохраняются автоматически и используются для эффективного обновления.

Простой запрос и обновление

Например, рассмотрим простую модель с участием блогов и публикаций:

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

e.Posts).First(e => e.Name == ".NET Blog"); blog.Name = ".NET Blog (Updated!)"; foreach (var post in blog.Posts.Where(e => !e.Title.Contains("5.0"))) < post.Title = post.Title.Replace("5", "5.0"); >context.SaveChanges(); -->

Вызов SaveChanges приводит к следующим обновлениям базы данных с использованием SQLite в качестве примера базы данных:

Представление отладки средства отслеживания изменений — это удобный способ визуализации отслеживаемых сущностей и их состояния. Например, перед вызовом SaveChanges вставьте следующий код в приведенный выше пример:

Будут получены следующие выходные данные:

Запрос, вставка, обновление и удаление

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

  • Блог и связанные с ним публикации запрашиваются из базы данных и отслеживаются.
  • Свойство Blog.Name изменяется.
  • В коллекцию существующих записей блога добавляется новая публикация.
  • Существующая публикация помечается для удаления путем вызова DbContext.Remove.

Еще раз взгляните на представление отладки средства отслеживания изменений перед вызовом команды SaveChanges. Вы увидите, как EF Core отслеживает эти изменения:

Обратите внимание на указанные ниже моменты.

  • Блог помечен как Modified . Это вызовет обновление базы данных.
  • Публикация 2 помечена как Deleted . Это вызовет удаление базы данных.
  • Новая публикация с временным идентификатором связана с блогом 1 и помечена как Added . Это вызовет вставку в базу данных.

Это приводит к выполнению следующих команд базы данных (при использовании SQLite) при вызове SaveChanges:

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

Ранее вы видели, что при внесении изменений в сущностные объекты Entity Framework отслеживает изменения в этих объектах. Благодаря этому, при вызове метода DbContex.SaveChanges(), Entity Framework знает, какие объекты нужно добавить в базу данных, какие обновить, а какие удалить. Для этого Entity Framework использует специальный механизм отслеживания изменений – .

Отслеживание изменений снимка данных (snapshot)

Отслеживание прокси-объектов (proxies objects)

Другой механизм для отслеживания изменений основывается на применении специальных прокси-объектов, которые уведомляют Entity Framework об изменениях в объектах сущностных классов. При рассмотрении отложенной загрузки мы уже говорили об использовании прокси-объектов. Прокси-объекты для отслеживания изменений создаются с помощью того же механизма, но в дополнение к отложенной загрузке, они также позволяют работать с изменениями в контексте. Чтобы использовать прокси-объекты, необходимо структурировать ваши классы модели таким образом, чтобы Entity Framework мог создать динамический тип во время выполнения, производный от класса модели. Этот тип переопределит каждое свойство модели для создания уведомлений при их изменении.

Использование снимка данных (snapshot)

Отслеживание изменений с помощью снимка данных используется по умолчанию и позволяет Entity Framework определить, когда происходят изменения в объектах модели. DbContext API обнаруживает многие изменения в данных с помощью снимка и вызывает метод DetectChanges() класса ChangeTracker, который не только обновляет информацию по данным, которые должны быть сохранены в базе данных, но также обрабатывает изменения при инициализации навигационных свойств и внешних ключей. Важно иметь четкое представление о том, как и когда изменения будут обнаружены, что от них ожидать и как их контролировать. В этом разделе рассматриваются эти проблемы.

Запуск любого запроса LINQ на объекте DbSet

Есть еще несколько методов, которые будут вызывать DetectChanges() и используются реже:

Управление вызовами DetectChanges()

Наиболее очевидным вызовом этого для отслеживания изменений, является вызов метода DbContext.SaveChanges(), который уведомляет Entity Framework о создании запроса к базе данных. Помимо этого мы можем вручную вызвать этот метод в любом месте, чтобы узнать какие данные изменились на текущий момент.

Сканирование данных не ограничивается только отслеживанием простых объектов. Например, в коде мы можем загрузить заказ из таблицы Orders базы данных, а затем добавить его в коллекцию заказов для нового покупателя. Объект, хранящий заказ, изменяется, потому что мы изменяем значение его навигационного свойства Customer и соответственно значение внешнего ключа UserId (если вы используете пример нашей модели, где мы определили внешний ключ явно). Но чтобы узнать, что это изменение произошло (или не произошло) Entity Framework необходимо сканировать все объекты типа Customer.

В большинстве случаев DetectChanges() вызывается достаточно быстро, что не вызывает проблем с производительностью. Тем не менее, если у вас есть очень большое количество объектов в памяти или вы выполняете много операций по DbContext с кучей запросов, то автоматический вызов DetectChanges() может серьезно ухудшить производительность приложения. К счастью у вас есть возможность выключить автоматические вызовы DetectChanges() и вызвать его вручную, когда это необходимо.

Entity Framework предполагает, что вы будете вызывать DetectChanges() перед каждым вызовом API, если вы изменили любой из объектов модели с момента последнего вызова API и перед запуском каких-либо запросов. Невыполнение этого требования может привести к неожиданным побочным эффектам. DbContext заботится об выполнении этого требования, при условии, что вы оставите включенный автоматический вызов DetectChanges(). Если вы выключите автоматические вызовы, то должны будете позаботиться о ручном вызове методов DetectChanges() там, где это необходимо.

Автоматические вызовы DetectChanges() можно включать и выключать с помощью логического свойства AutoDetectChangesEnabled, доступного в классе конфигурации контекста (DbContext.Configuration). Мы использовали эту настройку, когда определяли универсальный метод Inserts(), позволяющий вставлять несколько записей в таблицу. При условии, что мы бы вставляли сотни или тысячи записей с помощью этого метода, мы бы получили значительный выигрыш в производительности.

Давайте добавим метод, который отключает автоматические вызовы DetectChanges() и понаблюдаем за изменениями объектов:

В этом примере сначала отключается автоматический поиск изменений. Затем мы загружаем первого попавшегося покупателя из базы данных с фамилией Иванов и изменяем его фамилию на Петров. Т.к. мы отключили поиск изменений, при изменении сущностного объекта customer в памяти приложения, Entity Framework ничего не будет знать о его изменениях и о том, что нужно будет обновить запись в базе данных. Затем мы вручную запустили поиск изменений с помощью метода DetectChanges(). На рисунке ниже показан результат запуска приложения, с вызовом метода UsingDetectChanges из примера:

Запуск метода DetectChanges для обнаружения изменений в данных

Как и ожидалось, контекст не обнаружит изменений в сущностном объекте до тех пор, пока мы не вызовем метод DetectChanges() вручную.

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

Этот код позволяет избежать четырех ненужных вызовов DetectChanges(), которые бы произошли при вызовах методов DbSet.Add() и SaveChanges(). Этот пример используется исключительно для демонстрационных целей и не является реальным сценарием, при котором отключение DetectChanges() оказывает существенную выгоду.

Обновление навигационных свойств и внешних ключей

Метод DetectChanges() несет ответственность также за изменения значений в навигационных свойствах и внешних ключах. Если вы изменили некоторые отношения и хотели бы, чтобы все навигационные свойства и внешние ключи были синхронизированы, можно использовать метод DetectChanges(). Это может быть особенно полезно в сценариях привязки данных, где ваш графический интерфейс приложения будет изменять одно из навигационных свойств (или, возможно, внешний ключ), но при этом вы хотите, чтобы изменения сразу коснулись и связанных с ним объектов. Ниже показан пример:

Ручное обновление навигационных свойств с помощью метода DetectChanges

Как видите, до проверки изменений, ссылка на покупателя не обновилась в объекте order. Мы решили эту проблему вызовом метода DetectChanges(). Такой сценарий бывает полезен в приложениях, использующих графические платформы, например, WPF, где нужно сразу изменять пользовательский интерфейс при изменении данных.

Использование отслеживания прокси-объектов (proxies objects)

Если счетчик производительности выявил чрезмерное количество вызовов DetectChanges() или вы предпочитаете обрабатывать изменения в отношениях в реальном времени, есть еще один способ отслеживания изменений, используя прокси-объекты. Вам достаточно внести некоторые незначительные изменения в классы модели, чтобы Entity Framework мог создать прокси-объекты, унаследованные от этих классов и отслеживающие изменения. Следующие несколько правил описывают какую вы должны использовать структуру для классов модели:

класс должен быть открытым (модификатор) и не запечатанным (должно отсутствовать ключевое слово sealed);

каждое свойство должно быть помечено как виртуальное;

каждое свойство должно быть открыто для чтения и записи (get и set);

свойства навигации, имеющие тип коллекций, должны использовать тип ICollection <T>.

Обновите класс Customer в модели, как показано в примере ниже, чтобы удовлетворить эти требования:

Указание обобщенного типа коллекции ICollection<T> для навигационного свойства нужно затем, что прокси объект переопределяет это свойство и использует свой тип (EntityCollection<TEntity>). Этот тип коллекции будет отслеживать любые изменения в коллекции и сообщать их механизму Change Tracker.

Теперь, если вы запустите метод DetectRelationshipChanges(), то сможете убедиться, что изменения определяются автоматически с помощью прокси-объектов, еще до вызова метода DetectChanges(), как показано на рисунке ниже:

Прокси-объект обнаруживает изменение

Entity Framework автоматически создает прокси-объекты для результатов любых запросов, которые вы запускаете. Тем не менее, если вы просто используете конструктор вашего класса POCO для создания нового объекта, то он не будет прокси-объектом по умолчанию. Для того, чтобы получить прокси-объект без использования запроса, необходимо использовать метод DbSet.Create().

Для отключения отслеживания изменений только для определенных запросов, может использовать метод AsNoTracking(), который вызывается на экземпляре DbSet(). После этого, все запросы, связанные с этим объектом, не будут отслеживать в нем изменения данных.

Читайте также: