咨询区
ChsharpNewbie:
当我把大量的数据插入到数据库时 (PostgreSQL 12
和 Entity 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: FEHLERSqlState: 23505MessageText: 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: publicTableName: FileInfosConstraintName: PK_FileInfosFile: d:\pginstaller_12.auto\postgres.windows-x64\src\backend\access\nbtree\nbtinsert.cLine: 570Routine: _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:
我觉得你要做两点修改。
将 FirstOrDefault 改成 FirstOrDefaultAsync。
where 查询也是多余的。
改造后如下:
private async Task SaveFileInfos(FileInfo fileInfo){//update your code to use FirstOrDefaultAsyncvar 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 tripawait _context.SaveChangesAsync(); }
点评区
我个人感觉,这里报错的原因是: 本应该全异步的写法里面又掺杂了同步的写法,这是一种很鸡肋的做法,数据量稍微大一些之后就会有各种问题,这也是一个好的经验教训。