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());

Ресурс:

https://entityframework-extensions.net/bulk-update

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, но это только мое мнение.

Другие вопросы по тегам