Динамическое дерево выражений linq с вложенными свойствами
У меня есть список, который я должен отфильтровать по дочерним свойствам. Оператор фильтра является динамическим, и я использую построитель предикатов для объединения нескольких фильтров / лямбд.
Для простоты, скажем, у меня есть два класса, как это:
public class FirstClass
{
public int Id { get; set; }
public ICollection<SecondClass> MyList { get; set; }
}
public class SecondClass
{
public int ReferenceId { get; set; }
public int Value { get; set; }
}
Мой фильтр использует ссылочный идентификатор, тип оператора и значение, так что псевдокод будет выглядеть следующим образом:
"list of FirstClass".Where(param1 =>
param1.MyList.Single(param2 =>
param2.ReferenceId == "reference id").Value "operatorType" "value")
Фактический фильтр будет что-то вроде 123 eq 456
где ссылочный идентификатор равен 123, operatorType равен "eq", а значение равно 456.
Если оператор просто равенство, то следующее прекрасно работает:
Expression<Func<FirstClass, bool>> lambda =
param1 => param1.MyList.Single(param2 => param2.ReferenceId == id).Value == value;
Кроме того, фильтрация только на FirstClass
с динамическими выражениями, работает как шарм, например, фильтрация по Id (мой ExpressionTypeDictionary - это словарь для выбора ExpressionType
на основании предоставленного operatorType
):
var parameter = Expression.Parameter(typeof(FirstClass), "param1");
Expression body = parameter;
body = Expression.Property(body, "Id");
body = Expression.MakeBinary(ExpressionTypeDictionary[operatorType], body, value);
var lambda = Expression.Lambda<Func<FirstClass, bool>>(body, new[] { parameter });
Я могу получить следующее для компиляции, но выполнение фильтра на реальных данных с использованием EF Core возвращает исключение для querySource:
var parameter = Expression.Parameter(typeof(FirstClass), "param1");
Expression<Func<FirstClass, int>> left = param1 =>
param1.MyClass.Single(param2 => param2.ReferenceId == id).Value;
var body = Expression.MakeBinary(
ExpressionTypeDictionary[operatorType],
left.Body,
Expression.Constant(value));
var lambda = Expression.Lambda<Func<FirstClass, bool>>(body, new[] { parameter });
...
theList.Where(lambda);
Любые предложения приветствуются:)
1 ответ
Я думаю, а не выражение, как это
Expression<Func<FirstClass, bool>> predicate =
x => x.MyList.Single(y => y.ReferenceId == id).Value [operator] value;
Вы бы лучше построить выражение, как это:
Expression<Func<FirstClass, bool>> predicate =
x => x.MyList.Any(y => y.ReferenceId == id && y.Value == value);
Вот как вы можете это сделать:
var innerParameter = Expression.Parameter(typeof(SecondClass), "y");
var innerPredicate = Expression.Lambda<Func<SecondClass, bool>>(
Expression.AndAlso(
Expression.Equal(Expression.Property(innerParameter, "ReferenceId"), Expression.Constant(id)),
Expression.MakeBinary(ExpressionTypeDictionary[operatorType], Expression.Property(innerParameter, "Value"), Expression.Constant(value))),
innerParameter);
var parameter = Expression.Parameter(typeof(FirstClass), "x");
var predicate = Expression.Lambda<Func<FirstClass, bool>>(
Expression.Call(
typeof(Enumerable), "Any", new Type[] { typeof(SecondClass) },
Expression.Property(parameter, "MyList"), innerPredicate),
parameter);