如何在EF Core中直接对ICollection<T>类型属性应用Gridify过滤查询?
如何在EF Core中直接对ICollection类型属性应用Gridify过滤查询?
嘿,我完全理解你想直接从Authors表出发,用Gridify过滤关联特定Books的作者,不想绕去Books表反向查询的痛点。其实我们可以通过Gridify的自定义操作符来实现这个需求,下面给你具体的步骤和代码示例:
核心思路
Gridify本身默认不支持直接过滤ICollection<T>类型的属性,但我们可以通过自定义操作符,把针对集合的过滤条件转换成EF Core能识别的Any()或All()查询,这样就能直接从Authors表发起查询,EF Core会自动优化成SQL的EXISTS子查询,性能也有保障。
具体实现步骤
1. 配置Gridify自定义操作符
我们需要创建一个Gridify映射器,注册自定义操作符来解析集合属性的过滤逻辑。比如我们定义一个Any操作符,用来处理“作者拥有符合某条件的书籍”这类需求:
// 创建Gridify映射器并注册自定义操作符 var gridifyMapper = new GridifyMapper<Author>() .AddCustomOperator("Any", (propertyPath, filterValue) => { // 拆分属性路径,比如"Books.Name"拆分成集合属性"Books"和嵌套属性"Name" var propertyParts = propertyPath.Split('.'); var collectionPropertyName = propertyParts[0]; var nestedPropertyName = propertyParts[1]; // 构建LINQ表达式:a => a.Books.Any(b => b.Name == filterValue) var authorParam = Expression.Parameter(typeof(Author), "a"); var booksCollection = Expression.Property(authorParam, collectionPropertyName); var bookParam = Expression.Parameter(typeof(Book), "b"); var bookNameProp = Expression.Property(bookParam, nestedPropertyName); var filterConstant = Expression.Constant(filterValue); // 这里可以根据需求替换成Equals/Contains/GreaterThan等逻辑 var conditionExpr = Expression.Equal(bookNameProp, filterConstant); var bookLambda = Expression.Lambda(conditionExpr, bookParam); // 获取Enumerable.Any方法的泛型版本 var anyMethod = typeof(Enumerable) .GetMethods() .First(m => m.Name == "Any" && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(Book)); var anyExpr = Expression.Call(anyMethod, booksCollection, bookLambda); return Expression.Lambda<Func<Author, bool>>(anyExpr, authorParam); });
2. 在API接口中使用自定义映射器
修改你的GetAuthors方法,把自定义映射器传入ApplyFiltering方法:
[HttpGet] public async Task<IActionResult> GetAuthors([FromQuery] GridifyQuery query) { // 建议把自定义映射器抽成全局单例,避免每次请求重复创建 var gridifyMapper = new GridifyMapper<Author>() .AddCustomOperator("Any", (propertyPath, filterValue) => { var propertyParts = propertyPath.Split('.'); var collectionPropertyName = propertyParts[0]; var nestedPropertyName = propertyParts[1]; var authorParam = Expression.Parameter(typeof(Author), "a"); var booksCollection = Expression.Property(authorParam, collectionPropertyName); var bookParam = Expression.Parameter(typeof(Book), "b"); var bookNameProp = Expression.Property(bookParam, nestedPropertyName); var filterConstant = Expression.Constant(filterValue); var conditionExpr = Expression.Equal(bookNameProp, filterConstant); var bookLambda = Expression.Lambda(conditionExpr, bookParam); var anyMethod = typeof(Enumerable) .GetMethods() .First(m => m.Name == "Any" && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(Book)); var anyExpr = Expression.Call(anyMethod, booksCollection, bookLambda); return Expression.Lambda<Func<Author, bool>>(anyExpr, authorParam); }); var authors = DbContext.Authors .AsNoTracking() .ApplyFiltering(query, gridifyMapper); // 传入自定义映射器 var authorsDto = await ObjectMapper.ProjectTo<AuthorDto>(authors) .ToListAsync(); return Ok(authorsDto); }
3. 发起过滤请求
现在你可以通过查询参数传递集合过滤条件了:
- 要过滤拥有名为"ASP.NET入门"书籍的作者,可以用:
?filter=Books.Name:Any=ASP.NET入门 - 如果需要更复杂的匹配(比如包含关键词),可以扩展自定义操作符,比如新增
AnyContains操作符,把Expression.Equal替换成String.Contains的调用:
.AddCustomOperator("AnyContains", (propertyPath, filterValue) => { var propertyParts = propertyPath.Split('.'); var collectionPropertyName = propertyParts[0]; var nestedPropertyName = propertyParts[1]; var authorParam = Expression.Parameter(typeof(Author), "a"); var booksCollection = Expression.Property(authorParam, collectionPropertyName); var bookParam = Expression.Parameter(typeof(Book), "b"); var bookNameProp = Expression.Property(bookParam, nestedPropertyName); var filterConstant = Expression.Constant(filterValue); // 调用String.Contains方法 var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) }); var conditionExpr = Expression.Call(bookNameProp, containsMethod, filterConstant); var bookLambda = Expression.Lambda(conditionExpr, bookParam); var anyMethod = typeof(Enumerable) .GetMethods() .First(m => m.Name == "Any" && m.GetParameters().Length == 2) .MakeGenericMethod(typeof(Book)); var anyExpr = Expression.Call(anyMethod, booksCollection, bookLambda); return Expression.Lambda<Func<Author, bool>>(anyExpr, authorParam); });
然后用?filter=Books.Name:AnyContains=C#就能过滤出拥有名称包含"C#"书籍的作者。
注意事项
- 自定义映射器建议抽成全局单例,避免每次请求重复创建,提升性能
- EF Core会自动把
Any()查询转换成SQL的EXISTS子查询,不会加载整个Books集合,性能表现良好 - 可以根据需求扩展更多自定义操作符,比如
All(所有书籍都符合条件)、AnyGreaterThan等
备注:内容来源于stack exchange,提问作者AkshatSparrow
相关产品推荐
相关产品推荐

