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

C# EF:如何对SQLite文件进行内存中的单元测试?

批量运行xUnit测试时SQLite临时文件占用问题,及现有SQLite文件导入内存数据库方案

一、临时文件占用问题的解决

你遇到的批量测试失败问题,核心原因可能有两个:

  1. 临时文件名冲突:当前测试用temp_+原文件名生成临时文件,xUnit默认并行运行测试,多个测试会同时操作同一个临时文件,导致IO异常。
  2. 连接释放不彻底:即便调用了SqliteConnection.ClearPool,仍可能存在连接未及时关闭的情况。

优化方案:

  1. 生成唯一临时文件名:用Guid确保每个测试的临时文件独立,避免并行冲突。
  2. 使用using语句自动管理资源:替代手动调用Dispose,确保上下文和连接被及时释放。

修改后的测试代码:

[Theory]
[InlineData("飲newKanji_食欠人良resourceKanji_decks.anki2", new[] { 1707169497960, 1707169570657, 1707169983389, 1707170000793 }, 1707160682667)]
public void Move_notes_between_decks(string anki2File, long[] noteIdsToMove, long deckIdToMoveTo)
{
    //Arrange
    string originalInputFilePath = Path.Combine(_anki2FolderPath, anki2File);
    // 生成唯一临时文件名
    string tempFileName = $"temp_{Guid.NewGuid()}_{anki2File}";
    string tempInputFilePath = Path.Combine(_anki2FolderPath, tempFileName);
    File.Copy(originalInputFilePath, tempInputFilePath, true);
    
    // 使用using自动释放资源
    using var anki2Controller = new Anki2Controller(tempInputFilePath);
    List<Card> originalNoteDeckJunctions = anki2Controller.GetTable<Card>()
                                                        .Where(c => noteIdsToMove.Contains(c.NoteId))
                                                        .ToList();

    //Act
    bool movedNotes = anki2Controller.MoveNotesBetweenDecks(noteIdsToMove, deckIdToMoveTo);

    //Assert
    movedNotes.Should().BeTrue();
    List<Card> finalNoteDeckJunctions = anki2Controller.GetTable<Card>()
                                                        .Where(c => noteIdsToMove.Contains(c.NoteId))
                                                        .ToList();
    finalNoteDeckJunctions.Count.Should().Be(originalNoteDeckJunctions.Count);
    finalNoteDeckJunctions.Select(c => c.DeckId).Should().AllBeEquivalentTo(deckIdToMoveTo);

    //Cleanup
    // using会自动调用Dispose,无需手动调用
    File.Delete(tempInputFilePath);
}

同时调整Anki2Controller的Dispose方法,确保连接被正确关闭:

public void Dispose()
{
    var connection = (SqliteConnection)_context.Database.GetDbConnection();
    if (connection.State != ConnectionState.Closed)
    {
        connection.Close();
    }
    SqliteConnection.ClearPool(connection);
    _context.Dispose();
}

二、将现有SQLite文件导入内存数据库的方案

要把现有SQLite文件的数据导入到内存数据库,可以利用SQLite的备份功能,以下是基于EF Core的实现方案:

实现步骤:

  1. 创建内存数据库的上下文。
  2. 打开内存数据库连接(内存库必须保持连接打开才能使用)。
  3. 从原SQLite文件备份数据到内存数据库。

代码示例:

首先添加辅助方法创建内存数据库并导入数据:

private Anki2Context CreateInMemoryContextFromFile(string dbFilePath)
{
    // 配置内存数据库选项
    var options = new DbContextOptionsBuilder<Anki2Context>()
        .UseSqlite("Data Source=:memory:")
        .Options;

    var context = new Anki2Context(options);
    // 打开内存数据库连接
    context.Database.OpenConnection();

    // 从原文件数据库备份到内存数据库
    using var fileConnection = new SqliteConnection($"Data Source={dbFilePath}");
    fileConnection.Open();
    using var backup = new SqliteBackup(context.Database.GetDbConnection() as SqliteConnection);
    backup.BackupFrom(fileConnection);

    // 确保数据库架构存在
    context.Database.EnsureCreated();

    return context;
}

修改测试代码使用内存数据库:

[Theory]
[InlineData("飲newKanji_食欠人良resourceKanji_decks.anki2", new[] { 1707169497960, 1707169570657, 1707169983389, 1707170000793 }, 1707160682667)]
public void Move_notes_between_decks(string anki2File, long[] noteIdsToMove, long deckIdToMoveTo)
{
    //Arrange
    string originalInputFilePath = Path.Combine(_anki2FolderPath, anki2File);
    using var inMemoryContext = CreateInMemoryContextFromFile(originalInputFilePath);
    using var anki2Controller = new Anki2Controller(inMemoryContext);
    
    List<Card> originalNoteDeckJunctions = anki2Controller.GetTable<Card>()
                                                        .Where(c => noteIdsToMove.Contains(c.NoteId))
                                                        .ToList();

    //Act
    bool movedNotes = anki2Controller.MoveNotesBetweenDecks(noteIdsToMove, deckIdToMoveTo);

    //Assert
    movedNotes.Should().BeTrue();
    List<Card> finalNoteDeckJunctions = anki2Controller.GetTable<Card>()
                                                        .Where(c => noteIdsToMove.Contains(c.NoteId))
                                                        .ToList();
    finalNoteDeckJunctions.Count.Should().Be(originalNoteDeckJunctions.Count);
    finalNoteDeckJunctions.Select(c => c.DeckId).Should().AllBeEquivalentTo(deckIdToMoveTo);

    // 无需删除文件,内存数据库会在连接关闭后自动释放
}

注意事项:

  • 内存SQLite数据库必须保持连接打开才能使用,测试过程中不要提前关闭上下文连接。
  • 备份方法会完整复制原数据库的所有数据和架构,确保测试环境与原文件一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:55:53