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

如何在DBSet中用关键词列表过滤?EF Core查询翻译异常

问题详情

我在Controller中编写了如下查询代码:

var query = _context.Jobs.Where(x => x.Category.Equals(filterParams.Category) ||
filterParams.Query.Any(val => x.Title.Contains(val)));

FilterParams类定义如下:

public class FilterParams
{
    public string[] Query { get; set; }
    public CategoryEnum Category { get; set; }
}

其中Category过滤功能正常,但Title部分的过滤未达到预期效果。我希望当filterParams.Query为['FOO', 'BAR']时,返回标题包含这些关键词的实体(如“foo alice bar”“foo bar bob”)。

当前遇到的错误为:

System.InvalidOperationException: The LINQ expression 'val => EntityShaperExpression:
ynyn_be.Models.Job
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.Title.Contains(val)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.

我感到困惑的是,在过滤之后我已经调用了Skip()、Take()和ToListAsync(),为何仍被要求显式启用客户端评估?如何实现基于数组的过滤?


问题原因

EF Core的查询执行逻辑是先解析整个表达式树并转换为SQL语句,再执行查询。你的代码中filterParams.Query.Any(val => x.Title.Contains(val))是嵌套LINQ操作,EF Core无法将这种客户端数组的Any与数据库字段Contains的组合逻辑翻译成对应SQL,因此报错。

至于你提到的Skip()/Take()/ToListAsync(),这些都是查询执行阶段的操作,不会改变EF Core对Where子句的表达式解析规则——只要Where里的逻辑无法被翻译,不管后面加什么执行方法,都会触发错误。


解决方法

方法一:构建多条件OR查询(推荐,全数据库端执行)

把数组中的每个关键词转换成Title.Contains(keyword)的OR条件,EF Core可直接将这种逻辑翻译成SQL的WHERE (Title LIKE '%FOO%' OR Title LIKE '%BAR%')。

借助LinqKit库可以简化表达式合并:

// 先引用LinqKit.Microsoft.EntityFrameworkCore包
var query = _context.Jobs.AsExpandable()
    .Where(x => x.Category.Equals(filterParams.Category));

if (filterParams.Query != null && filterParams.Query.Length > 0)
{
    Expression<Func<Job, bool>> titleFilter = null;
    foreach (var keyword in filterParams.Query)
    {
        var currentKeyword = keyword; // 避免闭包陷阱
        if (titleFilter == null)
        {
            titleFilter = job => job.Title.Contains(currentKeyword);
        }
        else
        {
            titleFilter = titleFilter.Or(job => job.Title.Contains(currentKeyword));
        }
    }
    // 合并Category和Title的OR条件
    query = query.Where(job => job.Category.Equals(filterParams.Category) || titleFilter.Invoke(job));
}

var result = await query.Skip(...).Take(...).ToListAsync();

方法二:客户端评估(仅适合数据量小的场景)

如果符合Category过滤的数据量不大,可以先将数据拉到客户端再做Title过滤。这种方式无需修改表达式,但会加载所有符合Category的数据到内存,数据量大时性能差:

var result = await _context.Jobs
    .Where(x => x.Category.Equals(filterParams.Category))
    .AsEnumerable() // 切换到客户端评估
    .Where(x => filterParams.Query.Any(val => x.Title.Contains(val)))
    .Skip(...)
    .Take(...)
    .ToListAsync();

方法三:使用EF.Functions.Like(支持通配符)

如果需要更灵活的匹配规则,可用EF.Functions.Like替代Contains,支持自定义通配符:

var titleFilter = filterParams.Query.Aggregate(
    (Expression<Func<Job, bool>>)null,
    (acc, keyword) =>
    {
        var current = keyword;
        return acc == null 
            ? (Expression<Func<Job, bool>>)(job => EF.Functions.Like(job.Title, $"%{current}%")) 
            : acc.Or(job => EF.Functions.Like(job.Title, $"%{current}%"));
    });

var query = _context.Jobs.AsExpandable()
    .Where(job => job.Category.Equals(filterParams.Category) || titleFilter.Invoke(job));

内容的提问来源于stack exchange,提问作者MA-Moustache

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 12:03:36