Как использовать 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));