Generic Linq to Entities过滤器方法,它接受要过滤的过滤条件和属性

Hen*_*nda 2 c# generics linq-to-entities entity-framework

我已经在SO中查看了许多通用的linq过滤问题及其答案,但它们都没有满足我的需求,所以我想我应该创建一个问题.

我已经创建了许多我称之为"过滤器提供程序"的类,一个用于我的模型中的每个实体类,以便为我的应用程序提供简单的搜索.我不想进入像Lucene.Net这样的更高级的解决方案,因为具有匹配分数的基本过滤就足够了.

在这些提供者类的每个提供者类中,有多种方法将接收过滤术语和查询特定属性,并根据属性的相关性返回每个匹配的分数.大多数方法会一次过滤多个属性,但不是全部.

以下是其中两种方法:

private IQueryable<Retailer> MatchHighRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
    var results = retailers.Where(r =>
        (r.CompanyName != null && r.CompanyName.ToUpper().Contains(searchTerm))
        || (r.TradingName != null && r.TradingName.ToUpper().Contains(searchTerm))
    );

    return results;
}

private IQueryable<Retailer> MatchMediumRelevanceFields(string searchTerm, IQueryable<Retailer> retailers)
{
    var results = retailers.Where(r =>
        (r.Address.Street != null && r.Address.Street.ToUpper().Contains(searchTerm))
        || (r.Address.Complement != null && r.Address.Complement.ToUpper().Contains(searchTerm))
    );

    return results;
}
Run Code Online (Sandbox Code Playgroud)

这些方法在每个提供程序类中都被复制,我希望我可以将它们替换为接收要包含在查询中的属性的单个方法.

就像是:

public static IQueryable<T> Match<T>(string searchTerm, IQueryable<T> data, Expression<Func<T, string>> filterProperties)
{
    var results = **build the query for each property in filterProperties**

    return results;
}
Run Code Online (Sandbox Code Playgroud)

但我真的无法弄明白.我尝试使用反射,但它只适用于Linq到Objects,我需要Linq to Entities的解决方案.

Ser*_*rvy 5

所以要解决这个问题,我们首先需要一些拼图.第一个拼图是一个方法,它可以采用一个表达式来计算一个值,然后另一个表达式计算一个新值,它采用相同类型的第一个返回值,并创建一个新表达式来表示传递第一个结果的结果作为第二个参数.这允许我们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);
}
Run Code Online (Sandbox Code Playgroud)

这依赖于以下工具将一个表达式的所有实例替换为另一个:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我们还需要一个工具来帮助我们将OR两个谓词表达式组合在一起:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们已经有了这个,我们可以Compose在每个属性选择器上使用它来将它从属性结果映射到该属性值是否为非null并包含搜索项.然后,我们可以将所有这些谓词组合在一起,以获得查询的过滤器:

public static IQueryable<T> Match<T>(
    IQueryable<T> data,
    string searchTerm,
    IEnumerable<Expression<Func<T, string>>> filterProperties)
{
    var predicates = filterProperties.Select(selector =>
            selector.Compose(value => 
                value != null && value.Contains(searchTerm)));
    var filter = predicates.Aggregate(
        PredicateBuilder.False<T>(),
        (aggregate, next) => aggregate.Or(next));
    return data.Where(filter);
}
Run Code Online (Sandbox Code Playgroud)