如何在EF Core中通过字符串构建关联实体查询筛选表达式?
实现EF Core动态字符串筛选扩展方法(支持导航属性与Contains操作)
给定数据库表结构:
- 调色板表(Palettes)
| id | name |
|---|---|
| 1 | pa |
| 2 | pb |
- 颜色表(Colors)
| id | name | palette_id |
|---|---|---|
| 1 | ca | 1 |
| 2 | cb | 2 |
原本硬编码的筛选逻辑为:
_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
相关产品推荐
相关产品推荐

