WPF应用使用EF Core 5操作SQLite新增实体时出现并发冲突问题
我排查该问题的时间已经远超预期,且这是我首次将EF Core与SQLite搭配使用,目前仍未定位到问题根因。当前我开发的是基于.NET Core 3.1的单用户WPF应用,采用EF Core 5作为ORM框架,新增某一特定实体时触发报错,上下文内其余实体的新增、更新操作均无异常。
异常信息
新增实体时抛出的异常信息如下:Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
实体类定义
public class SourceDataElement { public int Id { get; set; } public int ColumnIndex { get; set; } // 外键关联到SourceDataRow public int RowId { get; set; } public SourceDataRow Row { get; set; } public string StringValue { get; set; } public int? IntegerValue { get; set; } public bool? BooleanValue { get; set; } public DateTime? DateTimeValue { get; set; } public Double? DoubleValue { get; set; } // 外键关联到SourceDataElementDataType public int SourceDataElementDataTypeId { get; set; } public SourceDataElementDataType SourceDataElementDataType { get; set; } public DateTime? LastUpdated { get; set; } [Timestamp] public byte[] Timestamp { get; set; } }
前置排查结论
我专门搭建了小型测试程序单独验证该实体的操作逻辑,尝试了多种调整方案都没有解决问题。我可以手动向数据库对应表中插入数据,说明问题出在EF Core的处理逻辑上。
新增实体的业务代码
var dateTimeStamp = DateTime.Now; var newSourceDataElement = new SourceDataElement() { ColumnIndex = 0, RowId = 1, StringValue = "Test Data", IntegerValue = null, BooleanValue = null, DateTimeValue = null, DoubleValue = null, SourceDataElementDataTypeId = 1, LastUpdated = dateTimeStamp }; _context.Add(newSourceDataElement); await _context.SaveChangesAsync();
EF Core生成的执行SQL
我查看了EF Core生成的执行SQL,看起来逻辑没有问题:
dbug: 10/18/2021 10:41:45.714 RelationalEventId.CommandExecuting[20100] (Microsoft.EntityFrameworkCore.Database.Command) Executing DbCommand [Parameters=[@p0=NULL, @p1='0' (DbType = String), @p2=NULL, @p3=NULL, @p4=NULL, @p5='2021-10-18T10:41:45.4850016-04:00' (Nullable = true) (DbType = String), @p6='1' (DbType = String), @p7='1' (DbType = String), @p8='Test Data' (Size = 9)], CommandType='Text', CommandTimeout='30'] INSERT INTO "SourceDataElements" ("BooleanValue", "ColumnIndex", "DateTimeValue", "DoubleValue", "IntegerValue", "LastUpdated", "RowId", "SourceDataElementDataTypeId", "StringValue") VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8); SELECT "Id", "Timestamp" FROM "SourceDataElements" WHERE changes() = 1 AND "rowid" = last_insert_rowid(); info: 10/18/2021 10:41:45.720 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (8ms) [Parameters=[@p0=NULL, @p1='0' (DbType = String), @p2=NULL, @p3=NULL, @p4=NULL, @p5='2021-10-18T10:41:45.4850016-04:00' (Nullable = true) (DbType = String), @p6='1' (DbType = String), @p7='1' (DbType = String), @p8='Test Data' (Size = 9)], CommandType='Text', CommandTimeout='30'] INSERT INTO "SourceDataElements" ("BooleanValue", "ColumnIndex", "DateTimeValue", "DoubleValue", "IntegerValue", "LastUpdated", "RowId", "SourceDataElementDataTypeId", "StringValue") VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8); SELECT "Id", "Timestamp" FROM "SourceDataElements" WHERE changes() = 1 AND "rowid" = last_insert_rowid();
已尝试的无效方案
- 给LastUpdated列添加[Concurrency]特性
- 新增插入和更新触发器自动维护timestamp字段
根因分析
问题出在你新增的维护Timestamp字段的触发器上:
EF Core完成插入操作后,会执行后续SELECT语句回查生成的Id和Timestamp值,WHERE条件中的changes()=1是判断插入操作是否成功。而你添加的触发器在插入完成后会自动更新Timestamp字段,导致SQLite的changes()函数返回的计数变为2,不满足changes()=1的条件,回查不到数据,EF Core就会判定插入操作影响行数为0,抛出并发异常。
可用解决方案
方案一(推荐):删除手动维护Timestamp的触发器
EF Core本身会在插入、更新实体时自动处理[Timestamp]标记的并发令牌字段的赋值,不需要额外通过触发器维护,直接删除你新增的插入、更新触发器即可解决问题。
方案二:在EF Core中配置数据库层面的Timestamp生成逻辑
如果你确实需要数据库层面维护Timestamp值,删除触发器后在DbContext的OnModelCreating方法中添加如下配置即可:
modelBuilder.Entity<SourceDataElement>() .Property(x => x.Timestamp) .IsRowVersion() .HasDefaultValueSql("CURRENT_TIMESTAMP") .ValueGeneratedOnAddOrUpdate();
方案三(临时验证用):移除并发令牌标记
暂时去掉Timestamp字段的[Timestamp]特性,如果插入操作恢复正常,即可100%确认是并发令牌的处理逻辑冲突导致的问题。
内容的提问来源于stack exchange,提问作者Monkey141

