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

如何在EF Core中通过字符串构建关联实体查询筛选表达式?

实现EF Core动态字符串筛选扩展方法(支持导航属性与Contains操作)

给定数据库表结构:

  • 调色板表(Palettes)
idname
1pa
2pb
  • 颜色表(Colors)
idnamepalette_id
1ca1
2cb2

原本硬编码的筛选逻辑为:

_dbContext.Palettes.Where(p => p.Colors.Any(x => x.Name.Contains("ca")))

以下是实现Filter扩展方法的完整方案,可接收类似"Colors.Name contains ca"的字符串并转换为EF Core表达式:


1. 定义实体类

先确保实体类正确配置导航关系:

public class Palette
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Color> Colors { get; set; } = new List<Color>();
}

public class Color
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int PaletteId { get; set; }
    public Palette Palette { get; set; }
}

2. 实现表达式解析核心逻辑

通过解析字符串中的属性路径、操作符、匹配值,构建对应的EF Core表达式:

using System.Linq.Expressions;
using System.Reflection;

public static class ExpressionBuilder
{
    // 解析属性路径,处理导航属性与普通属性
    private static Expression GetPropertyExpression(Expression parameter, string propertyPath)
    {
        var properties = propertyPath.Split('.');
        Expression propertyExpr = parameter;

        foreach (var prop in properties)
        {
            // 处理ICollection类型的导航属性
            if (propertyExpr.Type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ICollection<>)))
            {
                var elementType = propertyExpr.Type.GetGenericArguments()[0];
                var anyParam = Expression.Parameter(elementType, "x");
                var innerProp = Expression.Property(anyParam, prop);
                return new MemberExpressionWithAnyParam(anyParam, innerProp);
            }

            // 普通属性直接获取
            propertyExpr = Expression.Property(propertyExpr, prop);
        }

        return propertyExpr;
    }

    // 辅助类存储ICollection.Any的参数与内部属性表达式
    private class MemberExpressionWithAnyParam
    {
        public ParameterExpression AnyParameter { get; }
        public Expression InnerExpression { get; }

        public MemberExpressionWithAnyParam(ParameterExpression anyParam, Expression innerExpr)
        {
            AnyParameter = anyParam;
            InnerExpression = innerExpr;
        }
    }

    // 构建筛选表达式
    public static Expression<Func<T, bool>> BuildFilterExpression<T>(string filterString)
    {
        var parts = filterString.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
        if (parts.Length != 3)
            throw new ArgumentException("筛选字符串格式错误,需为:属性路径 操作符 值");

        var propertyPath = parts[0];
        var op = parts[1].ToLower();
        var value = parts[2];

        var parameter = Expression.Parameter(typeof(T), "p");
        var propertyExpr = GetPropertyExpression(parameter, propertyPath);

        Expression body;

        // 处理ICollection.Any + Contains的场景
        if (propertyExpr is MemberExpressionWithAnyParam anyContainer)
        {
            var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) })
                                 ?? throw new InvalidOperationException("未找到String.Contains方法");

            var containsCall = Expression.Call(anyContainer.InnerExpression, containsMethod, Expression.Constant(value));
            var anyLambda = Expression.Lambda(containsCall, anyContainer.AnyParameter);

            var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Public | BindingFlags.Static)
                               .Where(m => m.Name == "Any" && m.GetParameters().Length == 2)
                               .First()
                               .MakeGenericMethod(anyContainer.AnyParameter.Type);

            body = Expression.Call(anyMethod, Expression.Property(parameter, propertyPath.Split('.')[0]), anyLambda);
        }
        else
        {
            throw new NotSupportedException("当前仅支持ICollection导航属性的Contains筛选");
        }

        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }
}

3. 实现Filter扩展方法

将表达式逻辑封装为IQueryable的扩展方法:

public static class QueryableExtensions
{
    public static IQueryable<T> Filter<T>(this IQueryable<T> query, string filterString)
    {
        var filterExpr = ExpressionBuilder.BuildFilterExpression<T>(filterString);
        return query.Where(filterExpr);
    }
}

4. 使用示例

业务代码中直接调用扩展方法,效果与硬编码一致:

var filteredPalettes = _dbContext.Palettes.Filter("Colors.Name contains ca").ToList();

扩展说明

  • 若需支持更多操作符(如equals、startswith)或普通属性筛选,可扩展BuildFilterExpression中的逻辑。
  • 基础拆分逻辑不支持含空格的属性名或值,若需兼容可实现带引号解析的复杂拆分逻辑。
  • 需确保构建的表达式可被EF Core正确转换为SQL,避免出现客户端求值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:06:32