EF5 Секреты метода DetectChanges — II

Отключение автоматического определения изменений

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

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

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

Использование try\finally гарантирует, что автоматическое обнаружение изменений всегда включится обратно.

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

  1. Отсутствие вызовов методов EF приведет контекст к состоянию, когда DetectChanges() потребуется, если он не требовался до этого.
  2. Если свойства объекта меняется не через методы EF или же изменения в комплексном объекте, то может потребовать вызов метода DetectChanges().

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

Если ваш код меняет значения свойств в объектах, тогда согласно второму правилу, DetectChanges() должно быть вызвано, как минимум в составе SaveChanges(). Однако, этого можно избежать в силу правила 1, и оно может быть весьма полезно в сочетании с API для свойств из DbContext. Например, если вы хотите задать новое значение свойству без необходимости вызывать DetectChanges(), то можно поступить как в примере ниже:

В данном методе присоединяются в блог все указанные записи, а так же меняются названия постов и двигаются в другой блог, если они о EF.Если бы все описанные изменения производились непосредственно со свойствами сущностей, то вызов DetectChanges() потребовался бы, для того, чтобы проверить правильность данных перед записью в базу данных. Однако приведенный код использует методы Properties() и Reference() для работы со скалярными свойствами. Так как работа идет только с методами EF, то каждый их вызов будет произведен с нужными метками, проверками и прочим, что не потребует автоматического определения изменений. EF будет следить за непротиворечивостью данных, благодаря чему можно будет сохранить все в базу данных до включения обратно автоматического обнаружения изменений.

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

Подводя итог советам по использованию DetectChanges() можно сказать следующее:

  • Не выключайте автоматическое обнаружение изменений, пока вы твердо не убедились в том, что это нужно. Отключение только добавит кода и проблем, а не уменьшит их.
  • Если все же решили отключить обнаружение изменений, то делайте это локально с использованием try\finally. (Хотя в примере выше, можно не заключать, так как контекст «одноразовый»)
  • Используйте DbContext API для работы со свойствами для внесения изменений, так как эти методы не требуют запуска DetectChanges().

Сложные типы и бинарные свойства

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

EF поддерживает массивы байт для хранения произвольных данных, например, изображений. Можно сохранить баннер для блога добавив свойство типа массив байт к классу Blog:

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

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

Так же EF позволяет использовать комплексные свойства, т.е. такие, которые не представлены в схеме БД как самостоятельный элемент, не имеют ключей. Такое свойство можно также назвать комплексным объектом. Для примера можно рассмотреть запись о человеке с комплексным свойством «адрес», которое в свою очередь содержит комплексное свойство «телефонные номера»:

Заметьте, что класс Person полностью удовлетворяет условиям применения прокси отслеживания изменений. Можно подумать, что DetectChanges не нужен и такой код будет работать верно:

В результате выполнения этого кода, никаких изменений в базу данных внесено не будет! Причина в том, что даже если основной тип работал с прокси, адрес и телефонные номера не были обработаны для создания прокси (несмотря на виртуализацию свойств), так как EF никогда не делает прокси для комплексных типов. Вместо этого используется «снимок» данных и DetectChanges() всегда используется для комплексных объектов.

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

В такой интерпретации, все будет работать хорошо, данные уйдут в БД.

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

Данный пример работает с методами из EF для изменения свойств. Таким образом, вызов DetectChanges() не потребуется. Но как вы видите, за все придется платить, работа в таком стиле не выглядит изящной и лаконичной, раскрывающей смысл действия.

По мотивам статей Артура Викерса.

 

 

Hard’n’Heavy!

Оставить комментарий