Как массовое обновление записей в Entity Framework?

Я пытаюсь массово обновить записи, используя Entity Framework. Я пробовал Entity Framework. Расширения Update метод.

Update Метод может массово обновлять набор записей с одинаковым набором значений обновления.

Пример:

           Id -  Quantity
Record 1 - A  -  10
Record 2 - B  -  20
Record 3 - C  -  30

Мы можем массово обновить все вышеперечисленные записи простым вызовом

Records.Update(new => Record { Quantity = 100 });

Как я могу массово обновить каждую запись с различным количеством, используя Entityframework.Extensions или в любом другом подходе, который завершает массовое обновление быстрее?

10 ответов

Решение

Если вы не хотите использовать оператор SQL, вы можете использовать метод Attach, чтобы обновить сущность, не загружая ее сначала:

using (myDbEntities db = new myDbEntities())
{
    try
    {
      //disable detection of changes to improve performance
      db.Configuration.AutoDetectChangesEnabled = false;

      //for all the entities to update...
      MyObjectEntity entityToUpdate = new MyObjectEntity() {Id=123, Quantity=100};
      db.MyObjectEntity.Attach(entityToUpdate);

      //then perform the update
      db.SaveChanges();
    }
    finally
    {
      //re-enable detection of changes
      db.Configuration.AutoDetectChangesEnabled = true;
    }
}

Использование ExecuteSqlCommand:

using (yourDbEntities db = new yourDbEntities())
{
    db.Database.ExecuteSqlCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);
}

Или же ExecuteStoreCommand:

yourDbContext.ExecuteStoreCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);

Будет встроенный BulkUpdate()а также BulkDeleteметоды в EFCore, которые будут поставляться в EFCore 7.0

      context.Customers.Where(...).BulkDelete();
context.Customers.Where(...).BulkUpdate(c => new Customer { Age = c.Age + 1 });
context.Customers.Where(...).BulkUpdate(c => new { Age = c.Age + 1 });

Используйте этот способ, если вы просто хотите изменить несколько свойств:

   foreach (var vSelectedDok in doks)
                    {
                        //disable detection of changes to improve performance
                        vDal.Configuration.AutoDetectChangesEnabled = false;

                        vDal.Dokumente.Attach(vSelectedDok);

                        vDal.Entry(vSelectedDok).Property(x=>x.Status).IsModified=true;
                        vDal.Entry(vSelectedDok).Property(x => x.LastDateChanged).IsModified = true;
    }
    vDal.SaveChanges();

а) EFCore.BulkExtensions - BatchUpdateAsync

_dbContext.Set<MyObjectEntity>().BatchUpdateAsync( x => new MyObjectEntity{ Id=123, Quantity=100 });

https://github.com/borisdj/EFCore.BulkExtensions

Расширения EntityFrameworkCore: массовые операции (вставка, обновление, удаление, чтение, обновление, синхронизация) и пакетная обработка (удаление, обновление). Библиотека легкая и очень эффективная, в ней все чаще используются операции CRUD.Был выбран в 20 лучших рекомендуемых расширениях EF Core. от Microsoft".

б) Или EF Extensions - UpdateFromQuery

_dbContext.Set<MyObjectEntity>().UpdateFromQuery( x => new MyObjectEntity{ Id=123, Quantity=100 });

Ресурс:

https://entityframework-extensions.net/update-from-query

/questions/53611759/c-entity-framework-problema-s-vhodnoj-pamyatyu-massovyih-rasshirenij/53611774#53611774

Почему UpdateFromQuery быстрее, чем SaveChanges, BulkSaveChanges и BulkUpdate?

UpdateFromQuery выполняет инструкцию непосредственно в SQL, например UPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key].

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

Я нашел простой способ сделать это, добавив один общий метод расширенияSetValue вы можете просто написать:

void Main()
{
    
    var dc = this; // context
    var p = dc.Purchases.Where(x=>x.Description=="Bike")
                        .SetValue(w => w.Description = "Bicycle");
    p.Dump();
    dc.SubmitChanges();
}

Как видите, любое значение, соответствующее Where условию можно явно задать новое значение, поэтому здесь Bike будет заменен на Bicycle. После этого вы можете запросить таблицу, чтобы увидеть, что изменения действительно сохраняются.

Конечно, вы также можете опустить Where оператор, если вы хотите изменить все записи, например:

dc.Records.SetValue(x => x.Quantity = 100);
dc.SubmitChanges();

И вот метод расширения, использованный выше, который делает трюк (сторонние пакеты не требуются):

// see: https://visualstudiomagazine.com/articles/2019/07/01/updating-linq.aspx
public static class Extensions
{
    public static IEnumerable<T> SetValue<T>(this IEnumerable<T> items, Action<T>
         updateMethod)
    {
        foreach (T item in items)
        {
            updateMethod(item);
        }
        return items;
    }
}

Примечание. В этом примере используется пример базы данных Nutshell, которую вы можете легко создать, перейдя по этой ссылке, а код написан для LinqPad 6, но может быть легко адаптирован (LinqPad 6 использует.NET Core, но вы также можете попробовать его с LinqPad 5. для.NET Framework).

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

РЕДАКТИРОВАТЬ: этот документ предполагает, что AddRange только оптимизирует обнаружение изменений. Это не меняет способа применения изменений к базе данных.

Я склонен сказать, что проблема здесь не в EntityFramework, а в ожидании того, как обновить записи в SQL. Если у вас есть 50 записей, и вы хотите изменить поле Количество для всех них на новое уникальное значение, то вам нужно будет использовать 50 команд. AFAIK, SQL просто не имеет механизма, чтобы понять, как обновить эти записи без отдельной команды для каждой записи. Это ограничение затем передается в EF.

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

  • Пакетное обновление - Как уже упоминалось в нескольких других ответах, вы можете использовать пакетное обновление, когда вы отправляете несколько операторов UPDATE одновременно в одной команде SQL.
  • Группировка ключей - SQL отлично справляется с обновлением нескольких строк с помощью одной команды UPDATE, если вы хотите задать для полей одинаковое значение во всех совпадающих записях в запросе. Вы можете использовать это в своих интересах, если вы можете сгруппировать количества, которые необходимо обновить. Например, если вы знаете, что вам нужно обновить 150 записей до количества 100, вы можете выполнить его как один запрос UPDATE с предложением WHERE, чтобы включить все соответствующие идентификаторы.
  • Файлы XML:( - SQL имеет множество встроенных команд для обработки XML. Вы можете, могли бы использовать такую ​​команду, как UPDATE TBL SET TBL.QUANTITY = XMLTBL.QUANTITY FROM OPENXML(@idoc, '/ROOT/Items',1), См. https://docs.microsoft.com/en-us/sql/t-sql/functions/openxml-transact-sql для получения дополнительной информации.

Отчасти вопрос в том, насколько важно обновлять записи как можно быстрее? Конечно, мы все хотим, чтобы наши запросы были быстрыми, но если нам нужно реализовать что-то непонятное, чтобы получить такую ​​производительность, это может не стоить этого в долгосрочной перспективе. Например, не могли бы вы запустить обновление в фоновом потоке, чтобы оно не блокировалось?

Возможно с помощью UpdateRange([NotNullAttribute] params TEntity[] entities)

             private void bulkTagUpdate()
        {
            RfidTag tag = new RfidTag
            {
                Id = 1,
                Status ="tight",
                TagId = "234353444",
                LocationId = "1",
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now,
            };
            RfidTag tag2 = new RfidTag
            {
                Id = 2,
                Status = "tight",
                TagId = "3454544",
                LocationId = "1",
                CreatedAt = DateTime.Now,
                UpdatedAt = DateTime.Now,
            };

            List<RfidTag> tagList = new List<RfidTag>();
            tagList.Add(tag);
            tagList.Add(tag2);

            using (rfid_statusContext context = new rfid_statusContext())
            {
                context.RfidTags.UpdateRange(tagList);
                context.SaveChanges();
                MessageBox.Show("Update successfull !");
            }
        }

Массовое обновление может быть выполнено в три этапа с помощью простого EF вместо отдельных методов расширения:-

  • Сначала загрузите все объекты.
  • Обращайтесь к каждому объекту и изменяйте значения его полей.
  • После сохранения Foreach контекст меняется один раз.

Это отправит несколько запросов на обновление в одном пакете.

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