C# EF:如何对SQLite文件进行内存中的单元测试?
批量运行xUnit测试时SQLite临时文件占用问题,及现有SQLite文件导入内存数据库方案
一、临时文件占用问题的解决
你遇到的批量测试失败问题,核心原因可能有两个:
- 临时文件名冲突:当前测试用
temp_+原文件名生成临时文件,xUnit默认并行运行测试,多个测试会同时操作同一个临时文件,导致IO异常。 - 连接释放不彻底:即便调用了
SqliteConnection.ClearPool,仍可能存在连接未及时关闭的情况。
优化方案:
- 生成唯一临时文件名:用Guid确保每个测试的临时文件独立,避免并行冲突。
- 使用
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的实现方案:
实现步骤:
- 创建内存数据库的上下文。
- 打开内存数据库连接(内存库必须保持连接打开才能使用)。
- 从原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
相关产品推荐
相关产品推荐

