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

EF Core内存单元测试失败:Create-and-Attach删除方法测试异常

EF Core创建并附加式删除的单元测试问题解决建议

问题背景

为提升性能,仓储的DeleteAsync方法采用**创建并附加(create-and-attach)**的EF Core删除方式,而非先读取再删除。该方法在生产环境使用SQL Server时正常,但单元测试中使用InMemory数据库或Sqlite内存数据库时删除操作未生效,测试断言失败。

仓储核心代码:

public async Task DeleteAsync(int id)
{
    // DELETE: EF CORE create-and-attach approach
    Vehicle vehicleToDelete = new Vehicle() { Id = id };
    _context.Entry(vehicleToDelete).State = EntityState.Deleted;

    await _context.SaveChangesAsync();
}

问题原因

InMemory数据库特性限制

InMemory数据库是为测试设计的轻量级实现,当通过附加实体标记Deleted并调用SaveChangesAsync时,它只会记录删除操作,但不会自动从当前上下文的DbSet中移除该实体。直接访问repo.Vehicles.Count()会包含仍被上下文跟踪的已标记删除实体,导致断言错误。

Sqlite内存数据库使用误区

Sqlite内存数据库的生命周期与数据库连接绑定,连接关闭时数据会被销毁。测试中未显式打开连接,且未确保架构创建,导致数据无法持久化或查询失败。

解决方案

方案1:适配InMemory数据库的测试修改

修改测试中的断言逻辑,通过AsNoTracking()绕过上下文跟踪,直接查询数据库中的实际数据:

[Fact]
public async Task Can_Delete_Vehicle_With_InMemory()
{
    // Arrange
    var options = new DbContextOptionsBuilder<FredsCarsDbContext>()
        .UseInMemoryDatabase($"FredCars-{Guid.NewGuid().ToString()}")
        .Options;

    using var context = new FredsCarsDbContext(options);
    var repo = new EFVehicleRepository(context);

    Vehicle vehicle1 = new Vehicle
    {
        Status = Status.New,
        Year = "2025",
        Make = "Make",
        Model = "Gladiator Rubicon 4 X 4",
        Color = "Joose",
        Price = 64125,
        VIN = "1C6RJTBG0SL532163",
        VehicleTypeId = 3,
        ImagePath = "/images/jeeps/jeep5.jpg"
    };

    Vehicle vehicle2 = new Vehicle
    {
        Status = Status.Used,
        Year = "2020",
        Make = "Ford",
        Model = "Escape",
        Color = "Joose",
        Price = 22999,
        VIN = "1FMCU0F63LUC25826",
        VehicleTypeId = 1,
        ImagePath = "/images/cars/car2.webp"
    };

    await repo.CreateAsync(vehicle1);
    await repo.CreateAsync(vehicle2);
    // 用AsNoTracking验证初始数量
    Assert.Equal(2, await repo.Vehicles.AsNoTracking().CountAsync());

    // Act
    await repo.DeleteAsync(vehicle2.Id);

    // Assert
    // 绕过上下文跟踪,查询数据库实际数据
    Assert.Equal(1, await repo.Vehicles.AsNoTracking().CountAsync());
    var remainingVehicle = await repo.Vehicles.AsNoTracking().FirstOrDefaultAsync(v => v.Id == vehicle1.Id);
    Assert.Equal(vehicle1.VIN, remainingVehicle?.VIN);
}

方案2:正确使用Sqlite内存数据库

显式打开数据库连接,并确保创建数据库架构,避免连接关闭导致数据丢失:

[Fact]
public async Task Can_Delete_Vehicle_With_Sqlite()
{
    // Arrange
    var options = new DbContextOptionsBuilder<FredsCarsDbContext>()
        .UseSqlite("DataSource=:memory:")
        .Options;

    using var context = new FredsCarsDbContext(options);
    // 必须显式打开连接,否则内存数据库会被销毁
    await context.Database.OpenConnectionAsync();
    // 创建数据库架构
    await context.Database.EnsureCreatedAsync();

    var repo = new EFVehicleRepository(context);

    Vehicle vehicle1 = new Vehicle
    {
        Status = Status.New,
        Year = "2025",
        Make = "Make",
        Model = "Gladiator Rubicon 4 X 4",
        Color = "Joose",
        Price = 64125,
        VIN = "1C6RJTBG0SL532163",
        VehicleTypeId = 3,
        ImagePath = "/images/jeeps/jeep5.jpg"
    };

    Vehicle vehicle2 = new Vehicle
    {
        Status = Status.Used,
        Year = "2020",
        Make = "Ford",
        Model = "Escape",
        Color = "Joose",
        Price = 22999,
        VIN = "1FMCU0F63LUC25826",
        VehicleTypeId = 1,
        ImagePath = "/images/cars/car2.webp"
    };

    await repo.CreateAsync(vehicle1);
    await repo.CreateAsync(vehicle2);
    Assert.Equal(2, await repo.Vehicles.CountAsync());

    // Act
    await repo.DeleteAsync(vehicle2.Id);

    // Assert
    Assert.Equal(1, await repo.Vehicles.CountAsync());
    var remainingVehicle = await repo.Vehicles.FirstOrDefaultAsync(v => v.Id == vehicle1.Id);
    Assert.Equal(vehicle1.VIN, remainingVehicle?.VIN);
}

额外注意点

  • 若要更贴近生产环境行为,优先选择Sqlite内存数据库,它比InMemory更接近真实数据库的SQL执行逻辑。
  • 测试中若涉及多上下文操作,Sqlite内存库需要确保所有操作共享同一个打开的连接。

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 17:08:09