Generic Linq to Entities filter method that accepts filter criteria and properties to be filtered

前端 未结 4 717
死守一世寂寞
死守一世寂寞 2021-01-01 07:35

I\'ve looked into many generic linq filtering questions and their answers here in SO but none of them satisfy my needs so I thought I should create a question.

I\'ve

4条回答
  •  醉话见心
    2021-01-01 08:12

    So to solve this problem we need a few puzzle pieces first. The first puzzle piece is a method that can take an expression that computes a value, and then another expression that computes a new value taking the same type the first returns, and creates a new expression that represents the result of passing the result of the first function as the parameter to the second. This allows us to Compose expressions:

    public static Expression>
        Compose(
        this Expression> first,
        Expression> 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>(newSecond, param);
    }
    

    This relies on the following tool to replace all instances of one expression with another:

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

    We'll also need a tool to help us OR two predicate expressions together:

    public static class PredicateBuilder
    {
        public static Expression> True() { return f => true; }
        public static Expression> False() { return f => false; }
    
        public static Expression> Or(
            this Expression> expr1,
            Expression> expr2)
        {
            var secondBody = expr2.Body.Replace(
                expr2.Parameters[0], expr1.Parameters[0]);
            return Expression.Lambda>
                  (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
        }
    
        public static Expression> And(
            this Expression> expr1,
            Expression> expr2)
        {
            var secondBody = expr2.Body.Replace(
                expr2.Parameters[0], expr1.Parameters[0]);
            return Expression.Lambda>
                  (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
        }
    }
    

    Now that we have this we can use Compose on each property selector to map it from the property results to whether or not that property value is non-null and contains the search term. We can then OR all of those predicates together to get a filter for your query:

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

提交回复
热议问题