.NET中InMemory数据库下Concurrency Tokens影子列测试报错问题
解决Sqlite InMemory测试中xmin并发令牌的报错问题
问题根源
Sqlite的xmin是内置系统行版本列,由数据库自动维护,不允许手动插入或修改值。你通过TestDataBuilder手动填充该列,违反了Sqlite的系统列约束,导致报错。
解决方案步骤
1. 移除TestDataBuilder中对xmin的手动赋值
直接在构建实体时跳过xmin属性的设置,因为Sqlite会在插入/更新时自动生成并维护这个值。
2. 确保DbContext的并发令牌配置正确
针对Sqlite的xmin列,需要明确告诉EF该值由数据库生成,配置示例:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<YourEntity>() .Property(e => e.Xmin) .HasColumnName("xmin") // 映射到Sqlite系统列 .IsConcurrencyToken() // 标记为并发令牌 .ValueGeneratedOnAddOrUpdate(); // 指示值由数据库生成,EF无需处理 }
3. 测试时获取自动生成的xmin值
当你将实体添加到DbContext并调用SaveChanges()后,EF会自动将Sqlite生成的xmin值填充到实体的对应属性中,后续测试可直接使用该值:
// 构建并插入实体 var entity = new TestDataBuilder<YourEntity>().Build(); _context.YourEntities.Add(entity); _context.SaveChanges(); // 此时entity.Xmin已经被Sqlite自动赋值,可以用于后续测试
4. 并发冲突测试的正确写法
如果要验证并发冲突逻辑,无需手动修改xmin,使用两个独立的DbContext实例模拟多用户操作即可:
// 上下文1:插入并更新实体 using var context1 = CreateInMemoryDbContext(); var entity1 = new TestDataBuilder<YourEntity>().Build(); context1.YourEntities.Add(entity1); context1.SaveChanges(); entity1.Name = "Updated by Context1"; context1.SaveChanges(); // 上下文2:获取同一实体并尝试更新(此时xmin已过时) using var context2 = CreateInMemoryDbContext(); var entity2 = context2.YourEntities.Find(entity1.Id); entity2.Name = "Updated by Context2"; // 预期抛出并发异常 Assert.Throws<DbUpdateConcurrencyException>(() => context2.SaveChanges());
常见排查点
- 检查是否仍有代码在手动设置xmin的值(包括TestDataBuilder、测试初始化代码)
- 确认DbContext配置中是否添加了
ValueGeneratedOnAddOrUpdate(),否则EF会尝试将内存中的值写入数据库 - 确保在使用xmin值前已经调用了
SaveChanges(),未保存的实体xmin属性为默认值(如null),无法触发并发校验
内容的提问来源于stack exchange,提问作者Hanna Holasava
相关产品推荐
相关产品推荐

