Dynamic query with OR conditions in Entity Framework

后端 未结 3 1962
我在风中等你
我在风中等你 2020-12-15 02:37

I am creating an application that searches the database and allows the user to dynamically add any criteria (around 50 possible), much like the following SO question: Creat

3条回答
  •  暗喜
    暗喜 (楼主)
    2020-12-15 03:10

    While LINQKit and its PredicateBuilder are fairly versatile, it's possible to do this more directly with a few simple utilities (each of which can serve as the foundation for other Expression-manipulating operations):

    First, a general-purpose Expression Replacer:

    public class ExpressionReplacer : ExpressionVisitor
    {
        private readonly Func replacer;
    
        public ExpressionReplacer(Func replacer)
        {
            this.replacer = replacer;
        }
    
        public override Expression Visit(Expression node)
        {
            return base.Visit(replacer(node));
        }
    }
    

    Next, a simple utility method to replace one parameter's usage with another parameter in a given expression:

    public static T ReplaceParameter(T expr, ParameterExpression toReplace, ParameterExpression replacement)
        where T : Expression
    {
        var replacer = new ExpressionReplacer(e => e == toReplace ? replacement : e);
        return (T)replacer.Visit(expr);
    }
    

    This is necessary because the lambda parameters in two different expressions are actually different parameters, even when they have the same name. For example, if you want to end up with q => q.first.Contains(first) || q.last.Contains(last), then the q in q.last.Contains(last) must be the exact same q that's provided at the beginning of the lambda expression.

    Next we need a general-purpose Join method that's capable of joining Func-style Lambda Expressions together with a given Binary Expression generator.

    public static Expression> Join(Func joiner, IReadOnlyCollection>> expressions)
    {
        if (!expressions.Any())
        {
            throw new ArgumentException("No expressions were provided");
        }
        var firstExpression = expressions.First();
        var otherExpressions = expressions.Skip(1);
        var firstParameter = firstExpression.Parameters.Single();
        var otherExpressionsWithParameterReplaced = otherExpressions.Select(e => ReplaceParameter(e.Body, e.Parameters.Single(), firstParameter));
        var bodies = new[] { firstExpression.Body }.Concat(otherExpressionsWithParameterReplaced);
        var joinedBodies = bodies.Aggregate(joiner);
        return Expression.Lambda>(joinedBodies, firstParameter);
    }
    

    We'll use this with Expression.Or, but you could use the same method for a variety of purposes, like combining numeric expressions with Expression.Add.

    Finally, putting it all together, you can have something like this:

    var searchCriteria = new List>();
    
      if (!string.IsNullOrWhiteSpace(first))
          searchCriteria.Add(q => q.first.Contains(first));
      if (!string.IsNullOrWhiteSpace(last))
          searchCriteria.Add(q => q.last.Contains(last));
      //.. around 50 additional criteria
    var query = Db.Names.AsQueryable();
    if(searchCriteria.Any())
    {
        var joinedSearchCriteria = Join(Expression.Or, searchCriteria);
        query = query.Where(joinedSearchCriteria);
    }
      return query.ToList();
    

提交回复
热议问题