С ORM это всегда компромисс. Вам больше не нужно иметь дело с SQL в обмен на производительность. Несмотря на это, команда EF продолжает улучшать автоматически генерируемые SQL-запросы, всегда были операции, которые выполнялись плохо, как бы мы ни старались, поэтому конечным решением всегда было бы написание самого SQL.

Примером таких операций могут быть обновление и удаление. Давайте посмотрим, в чем именно проблема с ними, и как это можно исправить.

Обновлять

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

Чтобы обновить один объект, вы, вероятно, сделаете что-то вроде этого:

Здесь нет ничего сверхъестественного (пока 😈). Нам нужно загрузить нашу сущность в память, чтобы механизм отслеживания dbContext узнал об этом, затем мы делаем обновление и сохраняем все.

Приведенный выше код сгенерирует следующий SQL:

Обратите внимание, что мы выбираем поле Age, даже если оно не нужно.

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

Один объект без запроса Select

Что, если мы действительно хотим быть эффективными и избежать этого избыточного запроса SELECT. Еще возможно:

Просто создайте объект с заданным ID и полем, которое вы хотите изменить, прикрепите этот объект, чтобы механизм отслеживания dbContext узнал об этом, укажите, какое поле изменено, и все готово.

Сгенерированный SQL:

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

Хорошо, идем дальше.

Обновление нескольких сущностей

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

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

Это приведет к следующему SQL:

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

Господи Иисусе, никто не сказал мне, что 😰Может быть, мы можем улучшить его с помощью этих причудливых AsyncEnumerable?

Неа. Я имею в виду, вы получили небольшой прирост производительности, но это все тот же SQL.

Хорошо, я видел этот метод ForEach(). Это работает?

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

Ладно, ладно, я сдался. Можем ли мы что-нибудь сделать?

Не было. Пока не появился EF 7. Новый метод называется ExecuteUpdate() и может использоваться как для одного, так и для нескольких объектов:

Наконец, мы получаем то, что хотим, один оператор SQL 😌:

Однако вы заметили отсутствие метода SaveChangesAsync()? Механизм отслеживания dbContext ничего не знает об этих объектах. В память ничего не загружается. Очевидно, что это огромное улучшение, но и с огромными затратами. Будьте осторожны, чтобы не работать с устаревшими сущностями:

Удалить

С операцией удаления ситуация почти такая же.

Хотите что-то удалить, сначала загрузите это в память:

Вы знаете идентификатор? Хорошо, мы можем не загружать его:

Однако удаление буча:

Приведет к аду SQL:

До EF 7:

И это то, с чем наш SQL Server справляется без проблем:

Не то чтобы это было невозможно раньше:

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

Краткое содержание

Здесь мы кратко обсудили различные способы обновления и удаления объектов из базы данных. Как видите, некоторые простые решения могут привести к огромным проблемам с производительностью SQL-сервера. С другой стороны, несмотря на то, что массовые операции работают быстрее, они могут привести к неожиданному поведению. Только ты сам выбираешь, что больше подходит.

Спасибо за прочтение.

Хлопайте, когда найдете что-то новое 👏

Купи мне кофе по ссылке ниже ☕️

И Подпишитесь, чтобы не пропустить больше статей об EF ✅