C # Entity Framework: проблема с входной памятью массовых расширений
В настоящее время я использую EF Extensions. Одного я не понимаю: "это должно помочь с производительностью"
однако размещение более миллиона записей в переменной List само по себе является проблемой памяти. Итак, если вы хотите обновить миллион записей, не сохраняя все в памяти, как это можно сделать эффективно?
Должны ли мы использовать for loop
, и обновлять партиями, скажем, 10 000? Есть ли у EFExtensions BulkUpdate какие-либо встроенные функции для поддержки этого?
Пример:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics'); // this creates IQueryable
await productUpdate.ForEachAsync(c => c.ProductBrand = 'ABC Company');
_dbContext.BulkUpdateAsync(productUpdate.ToList());
Ресурс:
https://entityframework-extensions.net/bulk-update
Ответы
Я нашел "правильный" способ расширения EF для массового обновления с условием, подобным запросу:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics')
.UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });
Это должно привести к правильному SQL UPDATE ... SET ... WHERE
без необходимости сначала загружать сущности, как указано в документации :
Почему
UpdateFromQuery
это быстрееSaveChanges
,BulkSaveChanges
иBulkUpdate
?
UpdateFromQuery
выполняет инструкцию непосредственно в SQL, напримерUPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key]
.Для других операций обычно требуется один или несколько циклов обработки базы данных, что снижает производительность.
Вы можете проверить рабочий синтаксис на этом примере скрипта dotnet , адаптированном из их примера BulkUpdate
.
Прочие соображения
К сожалению, никаких упоминаний о пакетных операциях для этого нет.
Перед тем, как делать такое большое обновление, возможно, стоит подумать о том, чтобы деактивировать индексы, которые могут быть у вас в этом столбце, и затем восстановить их. Это особенно полезно, если у вас их много.
Внимательно относитесь к условию в
Where
, если он не может быть переведен EF как SQL, тогда это будет сделано на стороне клиента, что означает «обычный» ужасный обходной путь «Загрузка - изменение в памяти - обновление»
На самом деле EF не для этого. Взаимодействие с базой данных EF начинается с объекта записи и вытекает из него. EF не может генерировать частичное ОБНОВЛЕНИЕ (т.е. не перезаписывать все), если изменение объекта не отслеживалось (и, следовательно, не было загружено), и аналогично он не может УДАЛИТЬ записи на основе условия вместо ключа.
Не существует эквивалента EF (без загрузки всех этих записей) для логики условного обновления / удаления, такой как
UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'
или же
DELETE FROM People
WHERE FirstName = 'Robert'
Выполнение этого с использованием подхода EF потребует от вас загрузки всех этих сущностей только для того, чтобы отправить их обратно (с обновлением или удалением) в базу данных, а это, как вы уже обнаружили, является пустой тратой полосы пропускания и производительности.
Лучшее решение, которое я здесь нашел, - это обойти LINQ-дружественные методы EF и вместо этого самостоятельно выполнить необработанный SQL. Это все еще можно сделать с помощью контекста EF.
using (var ctx = new MyContext())
{
string updateCommand = "UPDATE People SET FirstName = 'Bob' WHERE FirstName = 'Robert'";
int noOfRowsUpdated = ctx.Database.ExecuteSqlCommand(updateCommand);
string deleteCommand = "DELETE FROM People WHERE FirstName = 'Robert'";
int noOfRowsDeleted = ctx.Database.ExecuteSqlCommand(deleteCommand);
}
Больше информации здесь . Конечно , не забудьте защитить от SQL-инъекций, где это необходимо.
Конкретный синтаксис для запуска необработанного SQL может варьироваться в зависимости от версии EF / EF Core, но, насколько мне известно, все версии позволяют выполнять необработанный SQL.
Я не могу конкретно комментировать производительность EF Extensions или BulkUpdate, и я не собираюсь покупать их у них.
Судя по их документации, похоже, что у них нет методов с правильными подписями, позволяющих использовать логику условного обновления / удаления.
BulkUpdate
похоже, не позволяет вам ввести логическое условие (WHERE в вашей команде UPDATE), которое позволит вам оптимизировать это.BulkDelete
по-прежнему естьBatchSize
параметр, который предполагает, что они все еще обрабатывают записи по одной (ну, я думаю, для каждой партии), а не используют один запрос DELETE с условием (предложение WHERE).
Исходя из вашего предполагаемого кода в вопросе, EF Extensions на самом деле не дает вам того, что вам нужно. Более производительно и дешевле просто выполнить необработанный SQL в базе данных, поскольку это позволяет EF не загружать свои сущности.
Обновление
Я могу исправить ошибку, есть некоторая поддержка логики условного обновления, как показано здесь . Однако мне непонятно, хотя пример все еще загружает все в память, и какова цель этой условной логики WHERE, если вы уже загрузили все это в память (почему бы тогда не использовать LINQ в памяти?)
Однако, даже если это работает без загрузки сущностей, это все равно:
- более ограниченный (разрешены только проверки равенства, по сравнению с SQL, допускающим любое логическое условие, которое является допустимым SQL),
- относительно сложный (мне не нравится их синтаксис, возможно, это субъективно)
- и дороже (все еще платная библиотека)
по сравнению с прокруткой собственного необработанного SQL-запроса. Я все еще предлагаю использовать здесь свой собственный необработанный SQL, но это только мое мнение.