EF 更新大量的数据时出现重复键错误

咨询区

  • ChsharpNewbie

当我把大量的数据插入到数据库时 (PostgreSQL 12Entity Framework Core),我得到了如下的报错。

fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
      Failed executing DbCommand (197ms) [Parameters=[@p0='?', @p1='?', @p2='?' (DbType = DateTimeOffset), @p3='?'], CommandType='Text', CommandTimeout='30']
      INSERT INTO "FileInfos" ("FileId", "FileName", "LastModifiedDateTime", "Path")
      VALUES (@p0, @p1, @p2, @p3);
fail: Microsoft.EntityFrameworkCore.Update[10000]
  An exception occurred in the database while saving changes for context type 'PostgreSQLConnect.ContextModels.WebhookContext'.
  Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.
   ---> Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint "PK_FileInfos 
    Severity: FEHLER
      SqlState: 23505
      MessageText: double key value violates unique constraint »PK_FileInfos«
      Detail: Detail redacted as it may contain sensitive data. Specify 'Include Error Detail' in the connection string to include this information.
      SchemaName: public
      TableName: FileInfos
      ConstraintName: PK_FileInfos
      File: d:\pginstaller_12.auto\postgres.windows-x64\src\backend\access\nbtree\nbtinsert.c
      Line: 570
      Routine: _bt_check_unique

这其中一些数据需要被更新,一些数据需要被创建,在一定数据量下这个方法比较稳定,但如果超过阈值后就会抛出如上的错误,我的代码如下:

private async Task SaveFileInfos(FileInfo fileInfo)
    {
        var foundFileInfo = _context.FileInfos.Where(f => f.FileId == fileInfo.FileId).FirstOrDefault();

        if (foundFileInfo == null)
        {
            await _context.FileInfos.AddAsync(fileInfo);
        }
        else
        {
            foundFileInfo.FileName = fileInfo.FileName;
            foundFileInfo.LastModifiedDateTime = fileInfo.LastModifiedDateTime;
            foundFileInfo.Path = fileInfo.Path;
        }

        await _context.SaveChangesAsync();
    }

我的类定义如下:

public class FileInfo : IFileInfo
    {

        [Key]
        public string FileId {get; set;}

        public string FileName {get; set;}

        public DateTimeOffset? LastModifiedDateTime {get; set;}

        public string Path {get; set;}
    }

Context类如下:

public class WebhookContext : DbContext
   {
        public WebhookContext(DbContextOptions<WebhookContext> options) : base(options) { }

        public DbSet<FileInfo> FileInfos { get; set; }
   }

然后在 loop 中做数据库保存。

private async Task ConvertAndSaveFiles(IDriveItemDeltaCollectionPage files)
 { 

    foreach (var file in files)
    {
         await SaveFileInfos(file.Name, file.Id, file.LastModifiedDateTime, file.ParentReference.Path);
    }
 }

请问我这是哪里写的有问题?

回答区

  • Edd

我觉得你要做两点修改。

  1. 将 FirstOrDefault 改成 FirstOrDefaultAsync。

  2. where 查询也是多余的。

改造后如下:

private async Task SaveFileInfos(FileInfo fileInfo)
    {
        //update your code to use FirstOrDefaultAsync
        var foundFileInfo = 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;
        }

       // move this outside the for loop.
       // this will round trip to Db in EVERY fileInfo, not an optimal solution.
        await _context.SaveChangesAsync(); 
    }

考虑到 SaveChangesAsync 是序列化到数据库,可以移到循环体外。

private async Task ConvertAndSaveFiles(IDriveItemDeltaCollectionPage files)
 { 

    foreach (var file in files)
    {
         await SaveFileInfos(file.Name, file.Id, file.LastModifiedDateTime, file.ParentReference.Path);
    }

    // this will save everything to Db in just 1 round trip
    await _context.SaveChangesAsync(); 
 }

点评区

我个人感觉,这里报错的原因是: 本应该全异步的写法里面又掺杂了同步的写法,这是一种很鸡肋的做法,数据量稍微大一些之后就会有各种问题,这也是一个好的经验教训。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值