Массовое удаление EF Core в PostgreSQL

Я пытаюсь выполнить потенциально крупномасштабную операцию удаления в одной таблице. (представьте, что 100 000 строк в таблице строк размером 1 м)

Я использую PostgreSQL и EntityFrameworkCore.

Подробности: код приложения имеет предикат для сопоставления и ничего не знает о том, сколько строк потенциально соответствует предикату. Это может быть 0 строк/с или очень большое количество.

Исследования показывают, что EF Core не может эффективно с этим справиться. (т. е. следующий код создает оператор Delete для каждой строки!)

Using (var db = new DbContext)
 var queryable = db.Table.AsQueryable()
       .Where(o => o.ForeignKey == fKey)
       .Where(o => o.OtherColumn == false);

 db.Table.RemoveRange(queryable);
 await db.SaveChangesAsync();

Итак, вот SQL, который я бы предпочел запускать в виде пакетной операции:

delete from Table
where ForeignKey = 1234
and OtherColumn = false
and PK in (
    select PK
    from Table
    where ForeignKey = 1234
    and OtherColumn = false
    limit 500
)

Существуют библиотеки расширений, но мне еще предстоит найти активную, поддерживающую Postgres. В настоящее время я выполняю необработанный sql выше через EF Core.

Это приводит к паре вопросов:

  1. Есть ли способ заставить EF Core более эффективно удалять эти строки в Postgres с помощью LINQ и т. д.? (Мне кажется, что передача контекста запрашиваемому должна дать ему все необходимое для принятия правильного решения здесь)
  2. Если нет, каково ваше мнение о пакетном удалении по сравнению с передачей БД только предиката?

🤔 А знаете ли вы, что...
PostgreSQL поддерживает шифрование данных и обеспечение безопасности.


3
2 182
3

Ответы:

Отказ от ответственности: Я владелец проекта Сущность Платформа Плюс

Ваш сценарий выглядит как то, с чем могут справиться наши функции Batch Delete: https://entityframework-plus.net/batch-delete

Using (var db = new DbContext)
 var queryable = db.Table.AsQueryable()
       .Where(o => o.ForeignKey == fKey)
       .Where(o => o.OtherColumn == false);

queryable.Delete();

Сущности не загружаются в приложение, и выполняется только SQL, как вы указали.


Я думаю, вы пытаетесь сделать что-то, для чего не следует использовать EntityFrameworkCore. Цель EntityFrameworkCore — иметь удобный способ перемещения данных между приложением .Net-Core и базой данных. Типичный useway - это один или небольшое количество объектов. Для массовых операций есть несколько nuget-пакетов. Существует пакет это для вставки и обновления с помощью postgres. Эта статья автора объясняет, как он использует временные таблицы и команду postgres COPY для выполнения массовых операций. Это показывает нам способ массового удаления строк по идентификатору:

var toDelete = GetIdsToDelete();
        using (var conn = new NpgsqlConnection(connectionString))
        {
            conn.Open();
            using ( var cmd = conn.CreateCommand())
            {
                cmd.CommandText =("CREATE TEMP TABLE temp_ids_to_delete (id int NOT NULL) ON COMMIT DROP ");
                cmd.Prepare();
                cmd.ExecuteNonQuery();
            }
            using (var writer  = conn.BeginBinaryImport($"COPY temp_ids_to_delete (id) FROM STDIN (FORMAT BINARY)"))
            {
                foreach (var id in toDelete)
                {
                    writer .StartRow();
                    writer .Write(id);
                }
                writer .Complete();
            }
            using (var cmd = conn.CreateCommand())
            {
                cmd.CommandText = "delete from myTable where id in(select id from temp_ids_to_delete)";
                cmd.Prepare();
                cmd.ExecuteNonQuery();
            }
            conn.Close();

С некоторыми небольшими изменениями это может быть более обобщенным.

Но вы хотите сделать что-то другое. Вы не хотите перемещать данные или информацию между приложением и базой данных. Вы хотите использовать efcore для создания slq-процедуры на лету и запускать ее на сервере. Проблема в том, что ядро ​​ef на самом деле не предназначено для этого. Но, возможно, есть способы обойти это. Один из способов, который я мог бы придумать, - использовать ef-core для создания запроса, получить строку запроса, а затем вставить эту строку в другую строку sql для запуска на сервере. Получить строку запроса в настоящее время непросто, но, очевидно, это будет с EF Core 5.0. Тогда вы можете сделать это:

var queryable = db.Table.AsQueryable()
   .Where(o => o.ForeignKey == fKey)
   .Where(o => o.OtherColumn == false);
var queryString=queryable.ToQueryString();
db.Database.ExecuteSqlRaw("delete from Table where PK in("+queryString+")" )

И да, это ужасно хаки, и я бы не рекомендовал этого. Я бы рекомендовал писать процедуры и функции на сервере базы данных, потому что это не то, для чего следует использовать ef-core. И тогда вы все еще можете запускать эти функции из ef-core и передавать параметры.


Я бы предложил использовать временные таблицы для выполнения такой операции. Вы должны создать зеркальную временную таблицу, массово добавить записи для сохранения или удаления в временную таблицу, а затем выполнить операцию удаления, которая ищет записи в/не в этой временной таблице. Попробуйте использовать такую ​​библиотеку, как PgPartner, чтобы очень легко выполнять массовое добавление и создание временных таблиц.

Проверьте PgPartner: https://www.nuget.org/packages/PgPartner/

https://github.com/SourceKor/PgPartner