C# Entity Framework: проблема с входной памятью массовых расширений
В настоящее время я использую EF Extensions. Одного я не понимаю: "это должно помочь с производительностью"
однако размещение более миллиона записей в переменной List - это проблема памяти. Итак, если вы хотите обновить миллион записей, не сохраняя все в памяти, как это можно сделать эффективно?
Должны ли мы использовать for loop
, а обновление пачками скажем по 10000? Есть ли у 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());
Ресурс:
2 ответа
Я нашел "правильный" способ расширения 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, но это только мое мнение.