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

XUnit并行测试引发EF Core与Sqlite数据异常问题

问题原因

你的DataContextFactory复用了同一个Sqlite连接对象,所有通过该工厂创建的DataContext都会操作同一个内存数据库实例。即使禁用测试并行,只要多个上下文共享连接,就会出现不同测试(或同一测试内的多个上下文)互相污染数据的情况——比如测试方法内同时使用构造函数创建的DataContext和GetContextAndRepository返回的DataContext,两者共享连接、操作同一个数据库,最终导致数据混乱。


解决方案

1. 重构DataContextFactory,确保每个上下文使用独立连接

修改工厂类,让每次创建上下文时生成新的Sqlite连接,使每个上下文对应独立的内存数据库,彻底隔离测试数据:

public class DataContextFactory : IDisposable
{
    private readonly List<SqliteConnection> _activeConnections = [];

    public DataContext Create()
    {
        // 每次创建全新的内存数据库连接
        var connection = new SqliteConnection("Filename=:memory:");
        connection.Open(); // 内存数据库必须保持连接打开,否则会被销毁
        _activeConnections.Add(connection);

        var options = new DbContextOptionsBuilder<DataContext>()
            .UseSqlite(connection)
            .Options;

        var context = new DataContext(options);
        context.Database.EnsureCreated(); // 新内存库为空,直接初始化表结构即可

        return context;
    }

    public void Dispose()
    {
        // 清理所有创建的连接
        foreach (var conn in _activeConnections)
        {
            conn.Dispose();
        }
        GC.SuppressFinalize(this);
    }
}

2. 调整测试基类,统一上下文管理

确保测试方法内的所有数据库操作都基于同一个上下文,避免同一测试内多个上下文共享数据库:

public abstract class CrudRepositoryTests<TTestEntity> : IDisposable
    where TTestEntity : BaseEntity
{
    private readonly DataContextFactory _factory = new();
    private readonly List<DataContext> _usedContexts = [];

    // 封装上下文和仓库的创建,确保每次返回独立实例
    protected (DataContext Context, IRepository<TTestEntity> Repository) GetTestDependencies()
    {
        var context = _factory.Create();
        _usedContexts.Add(context);
        var repository = CreateRepository(context); // 子类需实现仓库创建逻辑
        return (context, repository);
    }

    protected abstract IRepository<TTestEntity> CreateRepository(DataContext context);
    protected abstract ICollection<TTestEntity> GetEntitiesToAdd();

    public void Dispose()
    {
        // 清理所有使用过的上下文
        foreach (var ctx in _usedContexts)
        {
            ctx.Dispose();
        }
        _factory.Dispose();
        GC.SuppressFinalize(this);
    }

    [Fact]
    public virtual async Task GetAllAsync_ShouldReturnAllEntities()
    {
        // Arrange
        var (context, repository) = GetTestDependencies();
        var testEntities = await SetTestEntitiesAsync(context);

        // Act
        var result = await repository.GetAllAsync();

        // Assert
        Assert.Equal(testEntities.Count, result.Count());
    }

    protected async Task<ICollection<TTestEntity>> SetTestEntitiesAsync(DataContext context)
    {
        var existing = await context.Set<TTestEntity>().ToListAsync();
        if (existing.Count > 0)
        {
            return existing;
        }

        var newEntities = GetEntitiesToAdd();
        context.Set<TTestEntity>().AddRange(newEntities);
        await context.SaveChangesAsync();

        return newEntities;
    }
}

3. 验证测试隔离

  • 移除测试类构造函数中提前创建的DataContext,避免同一测试内存在多个上下文实例。
  • 确保所有测试操作都通过GetTestDependencies获取上下文和仓库,保证每个测试的数据库环境完全独立。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 18:34:51