2015-05-15 3 views
2

Я создал предикат, который выглядит как:Expression Tree с прикованными строковыми методами

p.Name.Contains("Saw") 

и я следующий код, который работает:

private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue) 
    { 
     PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName); 

     // ListOfProducts.Where(p => p.Contains(propertyValue)) 
     ParameterExpression pe = Expression.Parameter(typeof(T), "p"); 

     MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo); 
     MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}); 
     ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string)); 

     // Predicate Body - p.Name.Contains("Saw") 
     Expression call = Expression.Call(memberExpression, methodInfo, constantExpression); 

     Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe); 
     return lambda; 
    } 

Но я хочу, чтобы изменить предикат к:

p.Name.ToLower().Contains("Saw") 

и я подхожу. Я знаю, что мне нужно что-то добавить где-нибудь, где определяется MethodInfo.

У кого-нибудь есть предложение?

+1

Добавьте вызов метода к методу 'ToLower', так же, как у вас есть вызов метода для' метода Contains'. Это в основном тот же процесс, просто получите 'MethodInfo' и вызовите' Expression.Call'. – Servy

+2

Кстати, 'p.Name.ToLower(). Содержит (« Пила »)' всегда будет возвращать false. – StriplingWarrior

+0

@StriplingWarrior Ну, если текущая культура не является чем-то довольно незаметным, что на самом деле не делает то, что, по вашему мнению, должно делать (например, учитывая, что «S» - это строчная буква). – Servy

ответ

2

Вы должны получить выражение для метода ToLower, а затем использовать его в Contains выражении

private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue) 
{ 
    PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName); 

    ParameterExpression pe = Expression.Parameter(typeof(T), "p"); 

    MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo); 

    //ToLower expression 
    MethodInfo toLowerMethodInfo = typeof (string).GetMethod("ToLower", new Type[]{}); 
    Expression toLowerCall = Expression.Call(memberExpression, toLowerMethodInfo); 


    MethodInfo containsMethodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}); 
    ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string)); 

    // Pass ToLowerCall to 
    Expression call = Expression.Call(toLowerCall, containsMethodInfo, constantExpression); 

    Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe); 
    return lambda; 
} 
+0

@downvoter помочь объяснить? – Andre

1

Что касается ответа Андре, который, по существу, что я пришел после первого комментария Servy, я хотел после того, что я придумал:

private static Expression<Func<T, bool>> BuildContainsPredicate<T>(string propertyName, string propertyValue) 
    { 
     PropertyInfo propertyInfo = typeof (T).GetProperty(propertyName); 

     // ListOfProducts.Where(p => p.Contains(propertyValue)) 
     ParameterExpression pe = Expression.Parameter(typeof(T), "p"); 

     MemberExpression memberExpression = Expression.MakeMemberAccess(pe, propertyInfo); 
     // Thanks to Servy's suggestion 
     Expression toLowerExpression = Expression.Call(memberExpression, typeof(string).GetMethod("ToLower", Type.EmptyTypes)); 

     MethodInfo methodInfo = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}); 
     ConstantExpression constantExpression = Expression.Constant(propertyValue, typeof(string)); 

     // Predicate Body - p.Name.Contains("Saw") 
     Expression call = Expression.Call(toLowerExpression, methodInfo, constantExpression); 

     Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(call, pe); 
     return lambda; 
    } 
2

Вместо того, построение выражения в всем вручную только потому, что один крошечный маленький кусочек является динамическим, вы можете использовать регулярную лямбда, чтобы определить все содержимое, которое на самом деле является статичным, а затем просто заменить маленький бит, который не является.

В частности, общая стратегия, которую вы можете использовать, состоит в том, чтобы иметь лямбда с параметром, представляющим ваш маленький динамический бит, а затем использовать его в регулярной лямбда, а затем заменить все экземпляры этого параметра на динамически построенное выражение:

private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
    string propertyName, string propertyValue) 
{ 
    Expression<Func<string, bool>> e = s => s.ToLower().Contains(propertyValue); 
    var parameter = Expression.Parameter(typeof(T)); 
    var property = Expression.PropertyOrField(parameter, propertyName); 
    var body = e.Body.Replace(e.Parameters[0], property); 
    return Expression.Lambda<Func<T, bool>>(body, parameter); 
} 

Это не только упрощает исходный код, но и делает другие изменения в этот статический код, как легко, как редактирование любых обычные старые C# код, не требуя все манипуляции выражения, а также сложность (и потерю статической типизации), который приходит вместе с ним.

Это решение использует следующий метод, чтобы заменить все экземпляры одного выражения с другим:

public static Expression Replace(this Expression expression, 
    Expression searchEx, Expression replaceEx) 
{ 
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression); 
} 
internal class ReplaceVisitor : ExpressionVisitor 
{ 
    private readonly Expression from, to; 
    public ReplaceVisitor(Expression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 
    public override Expression Visit(Expression node) 
    { 
     return node == from ? to : base.Visit(node); 
    } 
} 

Другой подход, позволяя вещи, чтобы быть еще более высокий уровень, будет написать Compose метод, который позволяет вы легко формулируете выражения. Концептуально мы будем иметь две лямбды, и мы хотим, чтобы создать лямбда, который представляет собой вызывающую один и передавая ее результат в другой, а затем возвращает результат:

public static Expression<Func<TFirstParam, TResult>> 
    Compose<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first, 
    Expression<Func<TIntermediate, TResult>> second) 
{ 
    var param = Expression.Parameter(typeof(TFirstParam), "param"); 

    var newFirst = first.Body.Replace(first.Parameters[0], param); 
    var newSecond = second.Body.Replace(second.Parameters[0], newFirst); 

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param); 
} 

Это использует более или менее ту же стратегию, мы использовали выше, но он обобщает его вместо специального корпуса для ваших конкретных выражений.

У нас есть один оставшийся вспомогательный метод, который нужно сделать, прежде чем положить куски вместе; создание метода, который представляет доступ к свойству, как это определено имя строкового свойства:

public static Expression<Func<T, string>> MemberSelector<T>(string propertyName) 
{ 
    var param = Expression.Parameter(typeof(T)); 
    var body = Expression.PropertyOrField(param, propertyName); 
    return Expression.Lambda<Func<T, string>>(body, param); 
} 

Использование этих двух вспомогательных методов (которые не зависят от какой-либо конкретной ситуации), теперь мы можем построить лямбда, что мы хочу без каких-либо пользовательских построенного выражения манипуляции:

private static Expression<Func<T, bool>> BuildContainsPredicate<T>(
    string propertyName, string propertyValue) 
{ 
    return MemberSelector<T>(propertyName) 
     .Compose(prop => prop.ToLower().Contains(propertyValue)); 
}