Как динамически создать метод предиката из дерева выражений?
Вот сценарий:
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);
}
[это не то, что я пытаюсь сделать, кстати]
С 3 необязательными критериями я хочу написать что-то вроде:
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.
Таким образом, вопрос - есть ли способ динамически построить метод предиката?
ТИА
3 ответа
Чтобы сделать это для PagedCollectionView, вам нужно иметь предикат. Так это выглядит так:
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());
Я столкнулся с той же проблемой. У меня было 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
Благодаря подсказкам Бенджамина и этому посту -> Как конвертировать Func
Суть такова:
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;
}
Спасибо!