I am using EF Core to build a certain searching mechanism. I am providing a generic filter that is built in the front end like this for ex.: {"filter":{"logic":"and","filters":[{"field":"ObjectMaterial.MaterialId","value":"1","operator":"Eq"}, {"field":"ObjectMaterial.MaterialId","value":"2","operator":"Eq"}]},"sort":[],"take":20,"skip":0}
Now it is using reflection to build a LINQ query based on this filter, the problem now is that ObjectMaterial is a Collection based on the classes (simplified):
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; }
}
Whenever trying to filter based on the MaterialId, the program wont run and gives errors some way or another. The code below is where I am at right now. I want to achieve a LINQ query like this: ctx.VwObjectOverview.Where(o => o.ObjectMaterial.Any(om => om.MaterialId == 1 && om.MaterialId == 2))
for ex
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;
}
}
Aucun commentaire:
Enregistrer un commentaire