能否将SQL查询改写为LINQ?内存库测试遇FromSqlRaw兼容问题
解决方案:FromSqlRaw在内存数据库测试报错的问题
错误原因
EF Core的InMemory Provider基于内存集合实现,并非真正的SQL数据库,不支持解析执行原生SQL语句(FromSqlRaw),因此会抛出该错误。
可行方案
方案1:将原生SQL改写为LINQ查询
把原SQL逻辑转换成LINQ,既能让EF Core在真实数据库生成最优SQL执行,也能在InMemory Provider中正常运行,完美适配测试需求。
原SQL核心逻辑:
- 按
CmsId去重,保留每个CmsId的第一条记录 - 过滤条件:语言匹配 +(标题包含所有输入词 或 标签匹配任意输入词)
- 分页
对应的LINQ实现:
// 构建基础查询 var baseQuery = _context.Contents .Where(c => c.LanguageCode.ToLower() == language) .Where(c => // 标题过滤:所有输入词都在小写标题中存在(对应原SQL的strpos判断) phraseWords.All(word => c.Title.ToLower().Contains(word)) || // 标签过滤:至少有一个标签的小写名称等于任意输入词 c.TagsContents.Any(tc => phraseWords.Any(word => tc.Tags.Name.ToLower() == word)) ); // 处理DISTINCT ON (CmsId):EF Core 7+可用DistinctOn,旧版本用GroupBy var distinctQuery = baseQuery.DistinctOn(c => c.CmsId); // 旧版本替代写法: // var distinctQuery = baseQuery.GroupBy(c => c.CmsId).Select(g => g.FirstOrDefault()); // 分页并投影需要的字段 var results = await distinctQuery .Skip(offsetValue) .Take(resultsPerPage) .Select(c => new { c.CmsId, c.Id, c.Title }) .AsNoTracking() .ToListAsync(cancellationToken);
方案2:测试时改用SQLite内存数据库
SQLite是轻量级关系型数据库,支持原生SQL执行,替代InMemory Provider即可正常运行FromSqlRaw。
测试代码示例:
// 创建SQLite内存连接 using var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); // 配置DbContext使用SQLite var options = new DbContextOptionsBuilder<YourDbContext>() .UseSqlite(connection) .Options; // 初始化数据库并插入测试数据 using var context = new YourDbContext(options); context.Database.EnsureCreated(); // 插入测试Content、TagsContents、Tags数据... // 执行原FromSqlRaw逻辑 var results = await context.Contents .FromSqlRaw(query, queryParams) .AsNoTracking() .ToListAsync(cancellationToken);
方案3:抽象查询逻辑做隔离测试
如果不需要测试SQL本身,仅需验证业务逻辑,可以把查询封装成接口,测试时用Mock或内存集合模拟返回结果。
示例:
// 定义查询接口 public interface IContentRepository { Task<List<Content>> SearchContentsAsync(string language, List<string> words, int pageSize, int offset, CancellationToken ct); } // 生产环境实现(用原FromSqlRaw) public class ContentRepository : IContentRepository { // ... 原逻辑实现 } // 测试时用Mock var mockRepo = new Mock<IContentRepository>(); mockRepo.Setup(r => r.SearchContentsAsync(It.IsAny<string>(), It.IsAny<List<string>>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>())) .ReturnsAsync(testData); // 测试依赖该接口的业务逻辑
方案选择建议
- 优先选方案1:既保证SQL端执行的性能,又能无缝适配InMemory测试,代码更易维护。
- 若不想修改现有查询逻辑,选方案2:只需调整测试环境的数据库配置,成本最低。
- 若需完全隔离数据库做单元测试,选方案3:适合测试业务逻辑而非查询正确性。
内容的提问来源于stack exchange,提问作者Groomer
相关产品推荐
相关产品推荐

