Как использовать System.Linq.Expressions.Expression для фильтрации на основе детей?
У меня есть фильтр, который я использую во многих методах:
Expression<Func<Child, bool>> filter = child => child.Status == 1;
(на самом деле это сложнее)
И я должен сделать следующее
return db.Parents.Where(parent => parent.Status == 1 &&
parent.Child.Status == 1);
где условие такое же, как в вышеприведенном фильтре.
Я хочу повторно использовать фильтр в этом методе. Но я не знаю, как это сделать. Я попробовал
return db.Parents.Where(parent => parent.Status == 1 &&
filter(parent.Child));
но выражение не может использоваться как метод
Ответы
Ответ 1
Если вы хотите комбинировать выражения и по-прежнему можете использовать linq-to-sql, вам может потребоваться LinqKit. Он входит в ваше выражение и заменяет все вызовы функций их содержимым перед преобразованием sql.
Таким образом вы сможете напрямую использовать
return db.Parents
.AsExpandable()
.Where(parent => parent.Status == 1 && filter(parent.Child));
Ответ 2
Вы можете попробовать следующее:
var compiledFilter = filter.Compile();
foreach (var parent in db.Parents.Where(parent => parent.Status == 1))
if (compiledFilter(parent.Child))
yield return parent;
Это требует от вас всех родителей, но в отличие от решения @HugoRune, это не требует отношения 1:1 от родителя: ребенка.
Я не думаю, что это будет полезно для вашей ситуации из-за разных типов, но на всякий случай, вот пример того, как вы можете объединить Expression
s: Как объединить выражения LINQ в один?
Изменить: ранее я предлагал использовать Compile()
, но это не работает над LINQ-to-SQL.
Ответ 3
Хорошо, если есть связь 1:1 между родительским и дочерним
(маловероятно, но, похоже, это подразумевает этот пример), тогда вы можете сделать это следующим образом:
return db.Parents
.Where(parent => parent.Status == 1)
.Select(parent => parent.Child)
.Where(filter)
.Select(child=> child.Parent);
В противном случае это будет сложно.
Вы можете сделать это с помощью динамического linq, но это, вероятно, слишком велико.
Вы можете генерировать дерево выражений вручную, но это также довольно сложно. Я сам этого не пробовал.
В качестве последнего средства вы можете, конечно, всегда вызывать yourQuery.AsEnumerable()
, это заставит linq-to-sql перевести ваш запрос в sql до этого момента и выполнить остальную часть работы на стороне клиента; то вы можете .compile() ваше выражение. Однако вы теряете преимущества производительности linq-to-sql (и сам compile() довольно медленный, и когда он выполняется, он вызывает JIT-компилятор):
return db.Parents
.Where(parent => parent.Status == 1)
.AsEnumerable()
.Where(parent => filter.Compile().Invoke(parent.Child))
Лично я просто определяю выражение дважды, один раз для дочернего и один раз для parent.child:
Expression<Func<Child, bool>> filterChild = child => child.Status == 1;
Expression<Func<Parent, bool>> filterParent = parent => parent.Child.Status == 1;
Возможно, не самый элегантный, но, возможно, более простой в обслуживании, чем другие решения.
Ответ 4
Просто придумайте это, проверьте, будет ли это работать для вас
public interface IStatus { public int Status { get; set; } }
public class Child : IStatus { }
public class Parent : IStatus
{public Child Child { get; set; } }
Func<IStatus, bool> filter = (x) => x.Status == 1;
var list = Parents.Where(parent => filter(parent) && filter(parent.Child));
Надеюсь, это поможет!
Ответ 5
Не могли бы вы просто использовать выражение как функцию?
Вместо:
Expression<Func<Child, bool>> filter = child => child.Status == 1;
Используйте это же выражение как универсальная функция следующим образом:
Func<Child, bool> filter = child => child.Status == 1;
Затем вы сможете использовать функцию точно так же, как вы пытаетесь использовать выражение:
return db.Parents.Where(parent => parent.Status == 1 &&
filter(parent.Child));