You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL12搭配EF Core批量更新数据出现Duplicate key value错误如何解决

问题根因

你遇到的23505错误是PostgreSQL的主键唯一约束冲突,说明代码尝试插入重复的FileId值,核心原因是你的「存在则更新、不存在则新增」的判断逻辑在批量/重复数据场景下失效了,具体代码问题如下:

  • 输入集合未提前去重:如果传入的files中存在多个相同FileId的条目,判断逻辑可能重复触发新增操作
  • 未检查EF Core本地跟踪缓存:你当前的查询只查数据库,如果上下文已经跟踪了同FileId的新增实体但还未提交,再次查询数据库会返回空,导致重复Add
  • 同步查询在异步方法中使用:你用了同步的FirstOrDefault,不仅会阻塞线程,极端情况下也可能引发时序问题
  • 每次循环都提交事务:批量处理时每次调用SaveChangesAsync会产生大量数据库交互,性能差且增大冲突概率
  • 代码层面的判断无法应对并发场景:如果有多个线程/进程同时处理相同FileId的数据,两边同时查询都判定不存在,同时插入就会触发冲突

修复方案

方案1:优化现有判断逻辑(适合小数据量场景)

  1. 先对输入的文件集合去重,避免重复处理相同FileId
private async Task ConvertAndSaveFiles(IDriveItemDeltaCollectionPage files)
{ 
    // 先按FileId去重,保留最新的条目
    var distinctFiles = files.GroupBy(f => f.Id).Select(g => g.Last()).ToList();
    foreach (var file in distinctFiles)
    {
        var fileInfo = new FileInfo
        {
            FileId = file.Id,
            FileName = file.Name,
            LastModifiedDateTime = file.LastModifiedDateTime,
            Path = file.ParentReference.Path
        };
        await SaveFileInfos(fileInfo);
    }
    // 批量提交放到循环外,大幅提升性能
    await _context.SaveChangesAsync();
}
  1. 优化SaveFileInfos的查询逻辑,先查本地缓存再查数据库,改成异步查询
private async Task SaveFileInfos(FileInfo fileInfo)
{
    // 先查EF Core本地跟踪的实体,避免未提交的新增实体漏判
    var foundFileInfo = _context.FileInfos.Local.FirstOrDefault(f => f.FileId == fileInfo.FileId) 
        ?? await _context.FileInfos.FirstOrDefaultAsync(f => f.FileId == fileInfo.FileId);
    if (foundFileInfo == null)
    {
        await _context.FileInfos.AddAsync(fileInfo);
    }
    else
    {
        foundFileInfo.FileName = fileInfo.FileName;
        foundFileInfo.LastModifiedDateTime = fileInfo.LastModifiedDateTime;
        foundFileInfo.Path = fileInfo.Path;
    }
    // 已经把SaveChanges放到循环外,这里删掉原有的提交逻辑
}

方案2:使用PostgreSQL原生Upsert(推荐,适合批量/高并发场景)

直接用PostgreSQL的INSERT ON CONFLICT语法,在数据库层面处理冲突,比代码层面判断更可靠,性能也更高:

private async Task SaveFileInfos(FileInfo fileInfo)
{
    await _context.Database.ExecuteSqlInterpolatedAsync(@$"
        INSERT INTO ""FileInfos"" (""FileId"", ""FileName"", ""LastModifiedDateTime"", ""Path"")
        VALUES ({fileInfo.FileId}, {fileInfo.FileName}, {fileInfo.LastModifiedDateTime}, {fileInfo.Path})
        ON CONFLICT (""FileId"") DO UPDATE 
        SET ""FileName"" = EXCLUDED.""FileName"",
            ""LastModifiedDateTime"" = EXCLUDED.""LastModifiedDateTime"",
            ""Path"" = EXCLUDED.""Path"";
    ");
}

额外优化建议

如果单批次处理数据量超过1000条,建议每处理500-1000条就清空一次EF Core的跟踪缓存,避免内存占用过高:

_context.ChangeTracker.Clear();

内容的提问来源于stack exchange,提问作者ChsharpNewbie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 16:48:00