Массовое обновление и удаление. ExecuteUpdate и ExecuteDelete

Последнее обновление: 31.01.2023

В общем случае для обновления или удаления объектов в Entity Framework нам необходимо было сначала получить эти объекты. Контекст данных в EF Core по умолчанию отслеживает изменения в загруженных сущностях, а затем отправляет запросы на обновление в базу данных при вызове метода SaveChange(). Запросы на обновление отправляются только для тех свойств, которые действительно изменились. Кроме того, отслеживаемые объекты синхронизируются с изменениями, отправленными в базу данных. При этом изменения также группируются, чтобы уменьшить количество обращений к базе данных. Подобный механизм является довольно эффективным и удобным способом для работы с базой данных.

Однако нередко требуется просто обновить или удалить объекты без необходимости загружать их в контекст. В этом случае мы могли бы использовать sql-запросы. Но начиная с версии 7.0 в Entity Framework для типа IQueryable были добавлены два новых метода: ExecuteUpdate и ExecuteDelete и их асинхронные двойники ExecuteUpdateAsync и ExecuteDeleteAsync. Рассмотрим, в чем их преимущество.

Для рассмотрения возьмем следующий класс User и контекст данных ApplicationContext:

using Microsoft.EntityFrameworkCore;

public class ApplicationContext : DbContext
{
    public DbSet<User> Users { get; set; } = null!;
    public ApplicationContext() => Database.EnsureCreated();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=helloapp.db");
    }
}
public class User
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public int Age { get; set; }
}

ExecuteUpdate

В примерах выше, чтобы обновить объекты, сначала надо было их получить, установить у их свойств новые значения и затем сохранить новые значения в бд. И тут мы вынуждены совершать промежуточную операцию - получение объекта. Но он не всегда может быть нужен, возможно, просто надо обновить некоторые его свойства. И метод ExecuteUpdate() решает эту проблему. Допустим, нам надо у всех пользователей увеличить свойство Age на 1:

using (ApplicationContext db = new ApplicationContext())
{
    db.Users.ExecuteUpdate(s=>s.SetProperty(u=>u.Age, u=>u.Age +1));
}

Метод ExecuteUpdate() принимает делегат, параметр которого представляет набор выражений установки свойств (в примере выше представлен буквой s). Через этот параметр вызываем метод SetProperty(), в котором устанавливаем новое значение свойства.

Вызов метода SetProperty() принимает два параметра. Первый параметр определяет обновляемое свойство. В примере выше это выражение u=>u.Age. То есть обновляем свойство Age.

Второй параметр метода определяет, как изменяется значение свойства. В примере выше это выражение u=>u.Age + 1. То есть берем свойство Age и прибавляем к нему 1. Логика обновления может быть самой разной.

При выполнении такого вызова метода будет формироваться и выполняться следующий SQL-запрос:

UPDATE [u]
    SET [u].[Age] = [b].[Age] + 1
FROM [Users] AS [u]

Подобным образом можно использовать асинхронную версию метода:

using (ApplicationContext db = new ApplicationContext()) { await db.Users.ExecuteUpdateAsync(s=>s.SetProperty(u=>u.Age, u=>u.Age +1)); }

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

Если надо обновить не все, а какие-то определенные объекты, то перед вызовом ExecuteUpdate() определяем выборку. Например, обновим возраст только у тех объектов, у которых Name="Tom":

using (ApplicationContext db = new ApplicationContext())
{
    // обновляем только объекты, у которых имя Tom
    db.Users.Where(u=>u.Name=="Tom")
        .ExecuteUpdate(s=>s.SetProperty(u=>u.Age, u=>u.Age +1));
}

В этом случае формируется следующий sql-запрос:

UPDATE "Users" AS "u"
SET "Age" = "u"."Age" + 1
WHERE "u"."Name" = 'Tom'

С помощью дополнительных вызывов SetProperty() по цепочке можно обновлять несколько свойств одновременно. Например:

using (ApplicationContext db = new ApplicationContext())
{
    // обновляем только объекты, у которых имя Tom
    db.Users.Where(u =>u.Name == "Tom")
        .ExecuteUpdate(s => s
                .SetProperty(u => u.Age, u => u.Age + 1)    // Age = Age + 1
                .SetProperty(u=>u.Name, u => "Tomas"));      // Name = "Tomas

}

В данном случае к возрасту прибавляем 1 и изменяем имя на "Tomas".

В этом случае формируется следующий SQL-запрос:

UPDATE "Users" AS "u"
SET "Name" = 'Tomas',
    "Age" = "u"."Age" + 1
WHERE "u"."Name" ='Tom'

ExecuteDelete

Метод ExecuteDelete() позволяет избежать ненужного промежуточного получения объектов для удаления. Мы сразу можем удалить их одним запросом. Например, удалим всех пользователей, у которых Name="Tom":

using (ApplicationContext db = new ApplicationContext())
{
    // обновляем только объекты, у которых имя Tom
    db.Users.Where(u => u.Name == "Tom").ExecuteDelete();
}

Такой метод будет формировать следующий запрос:

DELETE FROM [u]
FROM [Users] AS [u]
WHERE [u].[Text] LIKE N'Tom'

Использование асинхронной версии:

using (ApplicationContext db = new ApplicationContext())
{
    // обновляем только объекты, у которых имя Bob
    await db.Users.Where(u => u.Name == "Bob").ExecuteDeleteAsync();
}

Можно сразу удалить все объекты:

using (ApplicationContext db = new ApplicationContext())
{
    db.Users.ExecuteDelete();
    // или асинхронно
    await db.Users.ExecuteDeleteAsync();
}

Такой метод будет формировать следующий запрос:

DELETE FROM [u]
FROM [Users] AS [u]

Недостатки методов

Выше говорилось о преимуществах методов ExecuteUpdate/ExecuteDelete. Но они также имеют недостатки. В частности, Любые отслеживаемые объекты в контексте данных не будут синхронизированы с теми изменениями, которые вызывают методы методов ExecuteUpdate/ExecuteDelete. Соответственно после изменения потребуется перезагружать данные из бд. Кроме того, возможно, потребуется отправить команды в правильном порядке, чтобы не нарушать ограничения базы данных. Например, удаленить зависимые сущности в БД до удаления привязанной главной сущности.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850