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

EF Core动态生成Where条件:电影标题与标签搜索问题

解决方案:EF Core实现电影标签+标题多词搜索

问题背景

数据库实体定义如下:

public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; }
    public ICollection<MovieTag> MovieTags { get; set; }
}

public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<MovieTag> MovieTags { get; set; }
}

public class MovieTag
{
    public int MovieId { get; set; }
    public Movie Movie { get; set; }
    public int TagId { get; set; }
    public Tag Tag { get; set; }
}

需要实现搜索功能:用户输入的所有单词,要么匹配电影的标签,要么电影标题包含所有这些单词(例如输入"test movie"时,返回标题含"test"和"movie"的电影)。现有代码仅支持标签搜索,之前尝试的All()方法和Dynamic LINQ均触发EF翻译异常。

使用环境:EF Core 7.0.9 + Npgsql.EntityFrameworkCore.PostgreSQL 7.0.4


方法一:手动构建LINQ表达式树(推荐)

通过手动组合表达式树,为每个关键词添加标题匹配条件,EF Core可正确翻译为SQL。

完整代码

// 处理输入关键词:拆分、去重、转小写
var phraseWords = phrase.Split(' ')
    .Where(x => x.Length > 1)
    .Select(x => x.Trim().ToLower())
    .Distinct()
    .ToList();

// 从Movie表开始查询,避免重复结果
var query = _context.Movies.AsNoTracking();

// 构建标题过滤条件:标题需包含所有关键词
Expression<Func<Movie, bool>> titleFilter = m => true;
foreach (var word in phraseWords)
{
    var currentWord = word; // 避免闭包捕获循环变量的问题
    titleFilter = titleFilter.And(m => EF.Functions.Like(m.Title.ToLower(), $"%{currentWord}%"));
}

// 构建标签过滤条件:电影关联的标签中存在匹配关键词的
var tagFilter = _context.MovieTags
    .Where(mt => phraseWords.Contains(mt.Tag.Name.ToLower()))
    .Select(mt => mt.MovieId);

// 合并条件:标签匹配 或 标题包含所有关键词
var results = query
    .Where(m => tagFilter.Contains(m.Id) || titleFilter.Invoke(m))
    .Include(m => m.MovieTags)
    .ThenInclude(mt => mt.Tag)
    .ToList();

补充:实现Expression的And扩展方法

如果项目中没有自带的And扩展方法,需自行实现:

public static class ExpressionExtensions
{
    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> left, Expression<Func<T, bool>> right)
    {
        var parameter = Expression.Parameter(typeof(T));
        var leftInvoke = Expression.Invoke(left, parameter);
        var rightInvoke = Expression.Invoke(right, parameter);
        var combined = Expression.AndAlso(leftInvoke, rightInvoke);
        return Expression.Lambda<Func<T, bool>>(combined, parameter);
    }
}

方法二:原生SQL查询

直接编写PostgreSQL原生SQL,利用数据库原生字符串匹配能力,适合复杂场景快速实现。

代码示例

var phraseWords = phrase.Split(' ')
    .Where(x => x.Length > 1)
    .Select(x => x.Trim().ToLower())
    .Distinct()
    .ToList();

var results = _context.Movies
    .FromSqlInterpolated($@"
        SELECT DISTINCT m.*
        FROM ""Movies"" m
        WHERE EXISTS (
            SELECT 1 
            FROM ""MovieTags"" mt
            JOIN ""Tags"" t ON mt.""TagId"" = t.""Id""
            WHERE mt.""MovieId"" = m.""Id"" 
              AND LOWER(t.""Name"") = ANY(array[{string.Join(",", phraseWords.Select(w => $"'{w}'"))}])
        )
        OR (
            {string.Join(" AND ", phraseWords.Select(w => $"LOWER(m.""Title"") LIKE '%{w}%'"))}
        )
    ")
    .Include(m => m.MovieTags)
    .ThenInclude(mt => mt.Tag)
    .AsNoTracking()
    .ToList();

注意:使用FromSqlInterpolated会自动参数化查询,避免SQL注入风险,不要直接拼接未处理的用户输入。


原方案失败原因

  1. All()方法:EF Core 7无法将本地集合的All操作翻译为SQL,因为All是针对内存集合的逻辑,EF无法转换为对应的数据库查询条件。
  2. Dynamic LINQ:字符串拼接时未正确传入关键词变量,且Dynamic LINQ对EF Core的表达式翻译支持有限,尤其是结合本地集合的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:03:27