PostgreSQL 上的 EF Core 批量删除

Posted

技术标签:

【中文标题】PostgreSQL 上的 EF Core 批量删除【英文标题】:EF Core Bulk Delete on PostgreSQL 【发布时间】:2019-06-20 23:29:34 【问题描述】:

我正在尝试对单个表执行潜在的大规模删除操作。 (想想 1m 行表上的 100,000 行)

我正在使用 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 的活跃库。我目前正在通过 EF Core 执行上面的原始 sql。

这引出了几个问题:

    有没有办法让 EF Core 使用 LINQ 等在 Postgres 上更有效地删除这些行? (在我看来,就像将可查询的上下文交给它应该给它在这里做出正确决定所需的一切) 如果不是,您对批量删除与仅将谓词交给数据库有何看法?

【问题讨论】:

【参考方案1】:

免责声明:我是项目的所有者Entity Framework Plus

您的场景看起来是我们的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。

【讨论】:

我试过了,使用PostgreSQL时会抛出异常【参考方案2】:

我认为您正在尝试做一些您不应该使用 EntityFrameworkCore 的事情。 EntityFrameworkCore 的目标是提供一种在 .Net-Core 应用程序和数据库之间移动数据的好方法。典型的使用方式是单个或少量对象。对于批量操作,有一些 nuget 包。有this 包用于使用 postgres 插入和更新。This article by the creator 解释了它如何使用临时表和 postgres COPY 命令进行批量操作。这向我们展示了一种通过 id 批量删除行的方法:

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-procedure 并在服务器上运行它。问题是 ef core 并没有真正做到这一点。但也许有办法解决这个问题。我能想到的一种方法是使用 ef-core 构建查询,获取查询字符串,然后将该字符串插入另一个 sql-string 以在服务器上运行。 获取查询字符串目前并不容易,但显然它将使用 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+")" )

是的,这非常骇人听闻,我不建议这样做。我建议在 databaseServer 上编写过程和函数,因为这不是 ef-core 应该用于的。然后你仍然可以从 ef-core 运行这些函数并传递参数。

【讨论】:

【参考方案3】:

我建议使用临时表来执行这样的操作。您将创建一个镜像临时表,将要保留或删除的记录批量添加到临时表中,然后执行删除操作以在该临时表中/不在该临时表中查找记录。尝试使用 PgPartner 等库来轻松完成批量添加和临时表创建。

查看 PgPartner:https://www.nuget.org/packages/PgPartner/

https://github.com/SourceKor/PgPartner

【讨论】:

以上是关于PostgreSQL 上的 EF Core 批量删除的主要内容,如果未能解决你的问题,请参考以下文章

对EF Core进行扩展使支持批量操作/复杂查询

如何使用 EF Core 7 批量删除数据

EF Core 慢速批量插入(~80k 行)

具有一对一关系的 EF Core 批量插入

为 ASP.NET Core 5.0 - EF Core 5.0 Web App 配置 PostgreSQL 连接字符串以在 MS 或 Linux 云上运行?

如果在单个请求中与 Oracle 和 PostgreSql 交互以处理超过 20 万条记录,如何提高 EF Core 性能