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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 11:52:16