【问题标题】:pass property/fieldname to predicate将属性/字段名传递给谓词
【发布时间】:2014-02-27 22:08:38
【问题描述】:

请原谅,我不完全确定我的问题措辞是否正确。 我正在创建一个搜索组件,用户可以在其中使用不同的运算符搜索不同的字段......例如description.contains(keywords) 和 measure.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); 

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

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

【问题讨论】:

  • 看看这里stackoverflow.com/a/10283288/1300049。或者您可以定义一些操作来获取所需的属性并将它们(操作)传递给您的搜索方法
  • 您可能会查看一些动态 Linq 选项。有些使用反射,有些则动态构建表达式树。不过,据我所知,这并不像您希望的那样直截了当。

标签: c# entity-framework generics lambda predicate


【解决方案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);
}

将查询过滤到给定字段中包含包含所有单词的单个字符串的项目似乎也很奇怪。您似乎更有可能想要获取包含 any 字符串列表的项目。那是不同的,只需要多做一点工作。

我们可以使用一个称为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);
    }
}

【讨论】:

  • 谢谢。这很有帮助。
【解决方案2】:

您可以使用 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

【讨论】:

  • 有没有办法让属性名仍然是强类型而不是字符串?
  • @Matt 我不确定,但我不这么认为。
  • @Matt 确保 PropertyName 与 ele 中的属性完全相同。
  • 您正在尝试获取该范围内不存在的变量的类型和值。
猜你喜欢
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多