将property / fieldname传递给谓词

时间:2014-02-27 22:08:39

标签: c# entity-framework generics lambda predicate

原谅我,我不完全确定我的问题措辞正确。 我正在创建一个搜索组件,用户可以使用不同的运算符搜索不同的字段... description.contains(keywords)和measurement.startsWith(yards).....

所以这就是我所拥有的:

void SearchDescription(IQueryable<MyClass> results, string keywords)
{
    switch(operator)
    { 
        case "Contains":
            results=results.Where(ele => ele.description.Contains(keywords));
            break;
        case "StartsWith":
            results = results.Where(ele => ele.description.StartsWith(keywords));
            break;
        ... and so on.....
    }
}

目前我对每个字段都有一个方法.... SearchDescription(),SearchID(),SearchMeasure()等。唯一的区别是字段/属性名称。

更新

进一步的研究可能是这样的:

void Search(IQueryable<Entity> results, string keywords, Expression<Func<Entity>,object>> predicate)
{
    results = results.Where(ele => predicate.Contains(keywords));
}

可以被称为:

Search(results, "my search terms", ele => ele.description); 

这显然不适用于它目前的形式,但也许这是对我所追求的更清晰的描述。

感谢目前为止的所有回复。

2 个答案:

答案 0 :(得分:-1)

您可以使用System.Reflection使用所需属性的名称检索PropertyInfo。请注意,反射有点慢,如果在一秒钟内多次使用,它会严重降低程序速度。

void Search(IQueryable<MyClass> results, string keywords, string propertyName)
{
    PropertyInfo elePropInfo = ele.GetType().GetProperty(propertyName);
    string elePropValue = (string)elePropInfo.GetValue(ele, null); // the second argument should be null for non-indexed properties
    switch(operator)
    { 
        case "Contains":
            results = results.Where(ele => elePropValue.Contains(keywords));
            break;
        case "StartsWith":
            results = results.Where(ele => elePropValue.StartsWith(keywords));
            break;
        // etc
    }
}

有关GetProperty()的更多信息,请访问MSDN:http://msdn.microsoft.com/en-us/library/kz0a8sxy(v=vs.110).aspx

答案 1 :(得分:-1)

这可以通过实现一个Compose方法来完成,该方法将接受两个表达式并返回一个表达式,就像它将调用第一个表达式一样,然后将其作为参数提供给第二个表达式:

void Search(IQueryable<Entity> results, string keywords,
    Expression<Func<Entity, string>> selector)
{
    results = results.Where(selector.Compose(obj => obj.Contains(keywords)));
}

为了实现这一点,我们将从一个帮助器方法开始,该方法允许我们将一个表达式的所有实例替换为另一个:

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);
    }
}

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

使用该工具就像把一些替换物一起塞进一个lambda一样简单:

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);
}

将查询过滤到包含给定字段中包含所有单词的单个字符串的项目似乎也很奇怪。您似乎更希望获得包含任何字符串列表的项目。这是不同的,只需要更多的工作。

我们可以使用我们称之为PredicateBuilder的新类来构建一个过滤器,该过滤器采用一堆其他过滤器的逻辑OR

void Search(IQueryable<Entity> results, IEnumerable<string> keywords,
    Expression<Func<Entity, string>> selector)
{
    var finalFilter = keywords.Aggregate(
        PredicateBuilder.False<Entity>(),
        (filter, keyword) => filter.Or(
            selector.Compose(obj => obj.Contains(keyword))));
    results = results.Where(finalFilter);
}

我们可以使用之前定义的Replace方法实现此类,如下所示:

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>() { return f => true; }
    public static Expression<Func<T, bool>> False<T>() { return f => false; }

    public static Expression<Func<T, bool>> Or<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
        return Expression.Lambda<Func<T, bool>>
              (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
    }
}