提问人:Jasper B 提问时间:11/13/2023 最后编辑:Svyatoslav DanylivJasper B 更新时间:11/14/2023 访问量:31
在 EF Core 中使用反射筛选多对多对象属性
Using reflection to filter on a many-to-many object property in EF Core
问:
我正在使用 EF Core 来构建某种搜索机制。我提供了一个在前端构建的通用过滤器,如下所示:
{
"filter": {
"logic": "and",
"filters": [
{ "field": "ObjectMaterial.MaterialId", "value": "1", "operator": "Eq" },
{ "field": "ObjectMaterial.MaterialId", "value": "2", "operator": "Eq" }
]
},
"sort": [],
"take": 20,
"skip": 0
}
现在它正在使用反射来构建基于此筛选器的 LINQ 查询,现在的问题是 ObjectMaterial 是基于类的集合(简化):
public partial class VwObjectOverview
{
public int ObjectId { get; set; }
public virtual ICollection<ObjectMaterial> ObjectMaterial { get; set; } = new List<ObjectMaterial>();
}
public partial class ObjectMaterial
{
[Key]
public int ObjectMaterialId { get; set; }
public int MaterialId { get; set; }
}
每当尝试根据 MaterialId 进行过滤时,程序都不会运行并以某种方式出现错误。下面的代码是我现在所处的位置。我想实现这样的 LINQ 查询: 对于 exctx.VwObjectOverview.Where(o => o.ObjectMaterial.Any(om => om.MaterialId == 1 && om.MaterialId == 2))
public static class QueryableExtensions
{
public static async Task<(IQueryable<TEntity> query, int count)> SearchWithFilter<TEntity>(this IQueryable<TEntity> query, List<SortDefinition>? sort = null, FilterDefinition? filter = null, int? skip = null, int? take = null)
{
if (filter != null && filter.Filters.Count > 0 && (filter.Field == null || filter.Value == null))
{
Expression<Func<TEntity, bool>> filterExpression = BuildFilterExpression<TEntity>(filter);
query = query.Where(filterExpression);
}
var count = await query.CountAsync();
if (sort != null)
{
foreach (SortDefinition sortDefinition in sort)
{
var parameter = Expression.Parameter(typeof(TEntity));
var property = Expression.Property(parameter, sortDefinition.Field);
var propAsObject = Expression.Convert(property, typeof(object));
var lambda = Expression.Lambda<Func<TEntity, object>>(propAsObject, parameter);
query = sortDefinition.Dir == SortDirection.Asc
? query.OrderBy(lambda)
: query.OrderByDescending(lambda);
}
}
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take > 0)
{
query = query.Take(take.Value);
}
return (query, count);
}
private static Expression<Func<T, bool>> BuildFilterExpression<T>(FilterDefinition filterDef)
{
var type = typeof(T);
var parameter = Expression.Parameter(type);
Expression filterExpr = BuildFilterExpression<T>(filterDef, parameter);
return Expression.Lambda<Func<T, bool>>(filterExpr, parameter);
}
private static Expression BuildFilterExpression<T>(FilterDefinition filterDef, Expression parameter)
{
if (filterDef.Filters != null && filterDef.Filters.Any())
{
// Build a nested filter expression by recursively calling this method on each nested filter
var filterExpressions = filterDef.Filters
.Select(nestedFilterDef => BuildFilterExpression<T>(nestedFilterDef, parameter))
.ToArray();
return filterDef.Logic == FilterLogic.And
? filterExpressions.Aggregate(Expression.AndAlso)
: filterExpressions.Aggregate(Expression.OrElse);
}
if (filterDef.Field == null || filterDef.Value == null)
return parameter;
// Split the field into parts if it contains a dot (indicating a related entity property)
var fieldParts = filterDef.Field.Split('.');
Expression propertyExpr = GetPropertyAccessExpression(parameter, fieldParts);
//Expression valueExpr = Expression.Constant(Convert.ChangeType(filterDef.Value, propertyExpr.Type));
var convertedValue = Convert.ChangeType(filterDef.Value, propertyExpr.Type);
Expression valueExpr = Expression.Constant(convertedValue);
valueExpr = Expression.Convert(valueExpr, propertyExpr.Type);
switch (filterDef.Operator)
{
case FilterOperator.Eq:
return Expression.Equal(propertyExpr, valueExpr);
default:
throw new NotSupportedException($"Filter operator {filterDef.Operator} is not supported.");
}
}
private static Expression GetPropertyAccessExpression(Expression parameter, string[] fieldParts)
{
Expression propertyAccess = parameter;
foreach (var fieldPart in fieldParts)
{
// If the property is a collection, use Any to check if any element satisfies the condition
if (propertyAccess.Type.IsGenericType && propertyAccess.Type.GetGenericTypeDefinition() == typeof(ICollection<>))
{
var elementType = propertyAccess.Type.GetGenericArguments()[0];
var elementParameter = Expression.Parameter(elementType);
var elementProperty = Expression.PropertyOrField(elementParameter, fieldPart);
// Create a predicate to use with Any
var predicate = Expression.Lambda(
BuildFilterExpression<object>(new FilterDefinition
{
Field = fieldPart,
Value = null, // You might need to adjust this depending on your use case
Operator = FilterOperator.IsNotNull
}, elementParameter),
elementParameter);
// Use Any to check if any element satisfies the condition
propertyAccess = Expression.Call(
typeof(Enumerable),
"Any",
new[] { elementType },
propertyAccess,
predicate);
}
else
{
// Access the property in the usual way
propertyAccess = Expression.PropertyOrField(propertyAccess, fieldPart);
}
}
return propertyAccess;
}
}
答: 暂无答案
评论