Dynamic query with OR conditions in Entity Framework

c# entity-framework linq linq-to-entities sql-server

Accepted Answer

ZZZ_tmp
21
11/18/2013 6:31:06 PM

Popular Answer

Even though LINQKit and its PredicateBuilder are quite flexible, it is feasible to accomplish this more directly with a few straightforward tools (each of which can act as the basis for other Expression-manipulating operations):

Initially, an all-purpose Expression Replacer

public class ExpressionReplacer : ExpressionVisitor
{
    private readonly Func<Expression, Expression> replacer;

    public ExpressionReplacer(Func<Expression, Expression> replacer)
    {
        this.replacer = replacer;
    }

    public override Expression Visit(Expression node)
    {
        return base.Visit(replacer(node));
    }
}

Next, a quick utility technique to use a different parameter in place of a given argument in an expression:

public static T ReplaceParameter<T>(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 required because, although having the same name, the lambda parameters in two separate expressions are essentially different parameters. For instance, if you wish to getq => q.first.Contains(first) || q.last.Contains(last) next, theq in q.last.Contains(last) definitely the very sameq It is given at the lambda expression's beginning.

Next, we require a multipurposeJoin a joining-capable techniqueFunc<T, TReturn> Together with a predetermined Binary Expression generator, -style Lambda Expressions.

public static Expression<Func<T, TReturn>> Join<T, TReturn>(Func<Expression, Expression, BinaryExpression> joiner, IReadOnlyCollection<Expression<Func<T, TReturn>>> 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<Func<T, TReturn>>(joinedBodies, firstParameter);
}

We'll apply this toExpression.Or however, the same approach might be used for a number of tasks, such as fusing numerical expressions withExpression.Add .

After putting everything together, you can have the following:

var searchCriteria = new List<Expression<Func<Name, bool>>();

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


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow