Как мне динамически построить метод предиката из дерева выражений?

Вот сценарий: Silverlight 4.0, DataGrid, источник элементов PagedCollectionView. Цель состоит в том, чтобы применить фильтр к PCV. Фильтр должен быть Predicate<object>(Method) - где Method реализует некоторую логику для объекта и возвращает true / false для включения. То, что у меня есть, - это необязательно включать 3 разных критерия в логику фильтра, и явный код быстро становится уродливым. Мы не хотим этого, не так ли?

Итак, я вижу, что есть способ построить дерево выражений с помощью PredicateBuilder и передать его в Linq.Where а-ля:

IQueryable<Product> SearchProducts (params string[] keywords)
{
  var predicate = PredicateBuilder.False<Product>();

  foreach (string keyword in keywords)
  {
    string temp = keyword;
    predicate = predicate.Or (p => p.Description.Contains (temp));
  }
  return dataContext.Products.Where (predicate);
}

[кстати, я не это пытаюсь сделать]

С тремя дополнительными критериями я хочу написать что-то вроде:

 Ratings.Filter = BuildFilterPredicate();  // Ratings = the PagedCollectionView


private Predicate<object> BuildFilterPredicate()
{
  bool FilterOnOrder = !String.IsNullOrEmpty(sOrderNumberFilter);
  var predicate = PredicateBuilder.False<object>();
  if (ViewMineOnly)
  {
    predicate = predicate.And(Rating r => sUserNameFilter == r.Assigned_To);
  }
  if (ViewStarOnly)
  {
    predicate = predicate.And(Rating r => r.Star.HasValue && r.Star.Value > 0);
  }
  if (FilterOnOrder)
  {
    predicate = predicate.And(Rating r => r.ShipmentInvoice.StartsWith(sOrderNumberFilter));
  }
  return predicate;
}

Конечно, это не будет компилироваться, потому что PredicateBuilder создает Expression<Func<T, bool>> не настоящий метод предиката. Но я вижу, что есть способы преобразовать дерево выражений в метод, поэтому мне казалось, что должен быть способ выполнить то, что мне нужно, не прибегая к кучке вложенных операторов if / then / else.

Итак, вопрос в том, есть ли способ динамически построить метод предиката?

TIA


person tobewan    schedule 20.09.2010    source источник


Ответы (3)


чтобы сделать это для PagedCollectionView, вам нужен Predicate. Так это выглядит:

private Predicate<object> ConvertExpressionToPredicate(Expression<Func<object, bool>> exp)
{
  Func<object, bool> func = exp.Compile();
  Predicate<object> predicate = new Predicate<object>(func);
  //Predicate<object> predicate = t => func(t);     // also works
  //Predicate<object> predicate = func.Invoke;      // also works
  return predicate;
}

и построим выражение:

private Expression<Func<object, bool>> BuildFilterExpression()
{
  ...snip...
  var predicate = PredicateBuilder.True<object>();
  if (ViewMineOnly)
  {
    predicate = predicate.And(r => ((Rating)r).Assigned_To.Trim().ToUpper() == sUserNameFilter || ((Rating)r).Assigned_To.Trim().ToUpper() == "UNCLAIMED");
  }
  if (ViewStarOnly)
  {
    predicate = predicate.And(r => ((Rating)r).Star.HasValue && ((Rating)r).Star.Value > 0);
  }
  if (FilterOnOrder)
  {
    predicate = predicate.And(r => ((Rating)r).ShipmentInvoice.Trim().ToUpper().StartsWith(sOrderNumberFilter));
  }
  if (ViewDueOnly)
  {
    predicate = predicate.And(r => ((Rating)r).SettlementDueDate <= ThisThursday);
  }
  return predicate;
}

а затем установите фильтр:

Ratings.Filter = ConvertExpressionToPredicate(BuildFilterExpression());
person tobewan    schedule 07.10.2010

Я столкнулся с той же проблемой. У меня было 3 критерия. Я сделал следующее:

  • Один метод проверки каждого критерия
  • Один метод проверки объекта

Код выглядел довольно чистым, и его было легко поддерживать.

Ratings.Filter = new predicate<objects>(validateObject);

private bool validateObject(object o)
{
  return validateFirstCriteria(o) && 
         validateSecondCriteria(o) && 
         validateThirdCriteria(o);
}

private bool validateFirstObject(object o)
{
  if (ViewMineOnly)
  {
    Rating r = o as Rating;
    if (o != null)
    {
      return  (r.Star.HasValue && r.Star.Value > 0);
    }
  }
  return false;
}
private bool validateSecondObject(object o)
{
  if (ViewStarOnly)
  {
    Rating r = o as Rating;
    if (o != null)
    {
      return sUserNameFilter == r.Assigned_To;
    }
  }
  return false;
}
private bool validateThirdObject(object o)
{
  if (FilterOnOrder)
  {
    Rating r = o as Rating;
    if (o != null)
    {
      return r.ShipmentInvoice.StartsWith(sOrderNumberFilter);
    }
  }
  return false;
}

ИЗМЕНИТЬ

Если вы хотите придерживаться деревьев выражений. Вы можете посмотреть здесь: http://msdn.microsoft.com/en-us/library/bb882536.aspx

Вы можете преобразовать дерево выражения в лямбда-выражение, а затем скомпилировать лямбда-выражение. После этого вы можете использовать это как метод. Пример:

        // The expression tree to execute.
        BinaryExpression be = Expression.Power(Expression.Constant(2D), Expression.Constant(3D));

        // Create a lambda expression.
        Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);

        // Compile the lambda expression.
        Func<double> compiledExpression = le.Compile();

        // Execute the lambda expression.
        double result = compiledExpression();

        // Display the result.
        Console.WriteLine(result);

        // This code produces the following output:
        // 8
person Benjamin Baumann    schedule 21.09.2010
comment
Спасибо, код очистился, но я все еще надеюсь найти решение на основе выражений. - person tobewan; 22.09.2010
comment
Отредактировано со ссылкой, которая может вам помочь. - person Benjamin Baumann; 23.09.2010

Благодаря подсказкам Бенджамина и этому сообщению -> Как преобразовать Func ‹T, bool› to Predicate ‹T›? Я разобрался.
Суть такова:


private static Predicate<T> ConvertExpressionToPredicate(Expression<Func<T, bool>> exp)
{
  Func<T, bool> func = exp.Compile();
  Predicate<T> predicate = new Predicate<T>(func);
  //Predicate<T> predicate = t => func(t);     // also works
  //Predicate<T> predicate = func.Invoke;      // also works
  return predicate;
}

Это скомпилирует дерево выражений в одну функцию и вернет предикат для вызова функции.
При использовании это выглядит так:

private static bool ViewStarOnly;
private static bool LongNameOnly;
static void Main(string[] args)
{
  List<Dabble> data = GetSomeStuff();
  ViewStarOnly = true;
  LongNameOnly = true;
  Expression<Func<Dabble, bool>> exp = BuildFilterExpression();
  List<Dabble> filtered = data.FindAll(ConvertExpressionToPredicate(exp));
  PrintSomeStuff(filtered);
}

private static Predicate<Dabble> ConvertExpressionToPredicate(Expression<Func<Dabble, bool>> exp)
{
  Func<Dabble, bool> func = exp.Compile();
  Predicate<Dabble> predicate = new Predicate<Dabble>(func);
  //Predicate<Dabble> predicate = t => func(t);     // also works
  //Predicate<Dabble> predicate = func.Invoke;      // also works
  return predicate;
}

private static Expression<Func<Dabble, bool>> BuildFilterExpression()
{
  var predicate = PredicateBuilder.True<Dabble>();
  if (ViewStarOnly)
  {
    predicate = predicate.And(r => r.Star.HasValue && r.Star.Value > 0);
  }
  if (LongNameOnly)
  {
    predicate = predicate.And(r => r.Name.Length > 3);
  }
  return predicate;
}

Спасибо!

person tobewan    schedule 30.09.2010