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

能否将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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 12:24:51