62

Based on my question from yesterday:

if I had to append to my existing 'where' expression, how would i append?

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    clientWhere = c => c.ClientFName == searchForClientFName;
}

 if (filterByClientLName)
    {
        clientWhere = c => c.ClientLName == searchForClientLName;
    }

The user can input either first name or last name or both. If they enter both i want to append to the expression. Trying to see if there is an equivalent of an append where i could do

clientWhere.Append or clientWhere += add new expression

or something similar

Community
  • 1
  • 1
  • I have been trying to find similar solution, because we use our old ORML tool developped by our team, which supports appending operations by "AND" or "OR" and our code heavily depends on such where extension. So far we could not switch to linq, but basically linq creates IExpression , and if you find a way around creating IExpression tree by yourself, that can help. – Akash Kava Aug 12 '09 at 15:05

8 Answers8

75

I believe you can just do the following:

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    var prefix = clientWhere.Compile();
    clientWhere = c => prefix(c) && c.ClientFName == searchForClientFName;
}
if (filterByClientLName)
{
    var prefix = clientWhere.Compile();
    clientWhere = c => prefix(c) && c.ClientLName == searchForClientLName;
}

If you need to keep everything in Expression-land (to use with IQueryable), you could also do the following:

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    Expression<Func<Client, bool>> newPred = 
        c => c.ClientFName == searchForClientFName;
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
        Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters);
}
if (filterByClientLName)
{
    Expression<Func<Client, bool>> newPred = 
        c => c.ClientLName == searchForClientLName;
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
        Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters);
}

This can be made less verbose by defining this extension method:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}

You can then use syntax like this:

Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}
Jason
  • 28,040
  • 10
  • 64
  • 64
  • 7
    Jason: Learning with all the info everyone has provided. I tried your way, by defining the extension in a static class, and I got an error: The binary operator AndAlso is not defined for the types 'System.Func`2[Models.Client,System.Boolean]' and 'System.Func`2[Models.Client,System.Boolean]'. –  Aug 12 '09 at 18:36
  • 5
    Even if you fix the bug in the extension method, this is a very fragile way to do this. See my answer to http://stackoverflow.com/questions/2231302/append-to-an-expression-c for details. – Eric Lippert Feb 09 '10 at 18:32
  • 3
    @Jason: Too many users with the display name "Jason" in my opinion. – jason Feb 09 '10 at 18:55
  • Where does AndAlso come from? – jjxtra Jun 23 '22 at 22:56
14

This is a complex scenario. You are almost building your own query engine on top of LINQ. JaredPar's solution (where did it go?) is great if you want a logical AND between all of your criteria, but that may not always be the case.

When I was wrangling with this in one of my project recently, I created two Lists:

List<Predicate<T>> andCriteria;
List<Predicate<T>> orCriteria;

(In this case, T is Client, for you)

I would populate the Lists with predicates that I want to be true. For instance,

decimal salRequirement = 50000.00;
andCriteria.Add(c => c.Salary > salRequirement);
orCriteria.Add(c => c.IsMarried);

Then, I would check against all the criteria in the Lists in my Where clause. For instance:

Expression<Func<Client, bool>> clientWhere =
    c => andCriteria.All(pred => pred(c) ) && orCriteria.Any(pred => pred(c) );

This could also be done with a for-loop for readability's sake. Remember to use the correct order of operations when applying your OR and AND clauses.

JoshJordan
  • 12,676
  • 10
  • 53
  • 63
  • Note that this also takes care of the case where you want to do more than just use "==", since the predicates could be *any* boolean function. – JoshJordan Aug 12 '09 at 15:10
  • 1
    Josh: if i build it with your style which is great by the way and then call the query: var query = from C in db.clients.Where(clientWhere) join O in db.orders.Where(orderWhere) on c.clientid equals O.clientid join P in db.products.Where(productWhere) on O.productid equals P.productid select new {C,O}; I get this error: Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator. –  Aug 12 '09 at 15:32
  • Not sure on that one - it may have to do with a specific criterion you're using. Have you tried it with empty criteria lists? – JoshJordan Aug 12 '09 at 15:49
  • Josh: my query returns IQueryable which i then send down to a pagination routine: public PaginatedList(IQueryable source, int pageIndex, int pageSize) { PageIndex = pageIndex; PageSize = pageSize; TotalCount = source.Count(); //THIS IS WHERE IT FAILS TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize); this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize)); } –  Aug 12 '09 at 16:21
11

If you encounter a similar problem, you can find all possible solutions in this great topic. Or just use PredicateBuilder is awesome helper for this poporse.

var predicate = PredicateBuilder.True<Client>();

if (filterByClientFName)
{
    predicate = predicate.And(c => c.ClientFName == searchForClientFName);
}

if (filterByClientLName)
{
        predicate = predicate.And(c => c.ClientLName == searchForClientLName);
}

var result = context.Clients.Where(predicate).ToArray();

It is some builder implementation.

public static class PredicateBuilder
    {
        // Creates a predicate that evaluates to true.        
        public static Expression<Func<T, bool>> True<T>() { return param => true; }

        // Creates a predicate that evaluates to false.        
        public static Expression<Func<T, bool>> False<T>() { return param => false; }

        // Creates a predicate expression from the specified lambda expression.        
        public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }

        // Combines the first predicate with the second using the logical "and".        
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }

        // Combines the first predicate with the second using the logical "or".        
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.OrElse);
        }

        // Negates the predicate.        
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
        {
            var negated = Expression.Not(expression.Body);
            return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
        }

        // Combines the first expression with the second using the specified merge function.        
        static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // zip parameters (map from parameters of second to parameters of first)
            var map = first.Parameters
                .Select((f, i) => new { f, s = second.Parameters[i] })
                .ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with the parameters in the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // create a merged lambda expression with parameters from the first expression
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        class ParameterRebinder : ExpressionVisitor
        {
            readonly Dictionary<ParameterExpression, ParameterExpression> map;

            ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;
                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }
                return base.VisitParameter(p);
            }
        }
    }
trueboroda
  • 2,650
  • 26
  • 24
  • 1
    was that an unintentional mistake to insert ParameterRebinder inside of PredicateBuilder? they are 2 different classes, 2 different approaches to basically achieve the same thing. PredicateBuilder is not EF friendly on its own, it needs some other classes in that extension library. The other it seems is EF friendly. Thanks for providing your source link, it is a great article! – Heriberto Lugo Jul 17 '21 at 18:45
6

Take a look at Predicate Builder, I believe this might work for you.

Vasu Balakrishnan
  • 1,751
  • 10
  • 15
3

Much easier and elegant solution from 2020 :)

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.And);
    }

Works for IQueryable.

Petr Savchenko
  • 188
  • 2
  • 12
  • Can you provide any documentation for the Compose method? – Kasun Koswattha Apr 22 '20 at 20:33
  • It can be found [here](https://learn.microsoft.com/en-us/archive/blogs/meek/linq-to-entities-combining-predicates). I just realized it seems over complicated for such simple task and duplicates solution provided by trueboroda above – Petr Savchenko Apr 24 '20 at 14:28
  • this is incomplete answer. compose does not exist, as it is a method created in a custom class. And also does not exist. this solution is misleading and does not offer anything useful. you took 1 method out of a custom class and did not include any of the other code it depends on. – Heriberto Lugo Jul 17 '21 at 18:51
  • Hi @HeribertoLugo, I think I admitted duplication in the comment and refer to trueboroda's [solution](https://stackoverflow.com/a/54416959/13193733) full of details. – Petr Savchenko Jul 20 '21 at 10:45
2

It´s not exactly the answer for your question, but, I was looking for the same thing you are, and then I've found a better answer to my question.

Instead of building a dynamic Expression, you could retrieve the IQueryable and then filter what you want like this:

var customers = CustomerRepository.AllEntities();

if (!forename.IsNullOrEmpty())
    customers = customers.Where(p => p.Forename == forename);
if (!familyname.IsNullOrEmpty())
    customers = customers.Where(p => p.FamilyNames.Any(n => n.Name==familyname));
if (dob.HasValue)
    customers = customers.Where(p => p.DOB == dob);

Note: I was concerned about executing more then one ".Where" statement because I was afraid this would generate more than one query in the DataBase, or because I would have to retrive all records and then filter them, but this is not true, Linq dynamic generate just one query only when you call .ToList() method.

Here you can see original question that I've took the example from.

Community
  • 1
  • 1
Daniel
  • 2,780
  • 23
  • 21
1

Or something to add to Josh (Put it in my bag of tricks):

public static IQueryable<TSource> ObjectFilter<TSource>(this TSource SearchObject, List<Predicate<TSource>> andCriteria, List<Predicate<TSource>> orCriteria) where TSource : IQueryable<TSource>
        {
            //Yeah :)
            Expression<Func<TSource, bool>> ObjectWhere = O => andCriteria.All(pred => pred(O)) && orCriteria.Any(pred => pred(O));
            return SearchObject.Where<TSource>(ObjectWhere);
        }
MarkWalls
  • 909
  • 8
  • 12
-1

I tried to implement this kind of stuff. Took me a day to find out. My solution is based on filter in a loop based on a Array of predicate. As a note, it s totally Generic and based Reflection because the only information about class and field are String. To make it simple, i call directly the Model class but in a project you should go by a controler who is calling the Model.

So here we go : The Model part where T is a Generic in the class

    public class DALXmlRepository<T> where T : class
    {
    public T GetItem(Array predicate)
    {
        IQueryable<T> QueryList = null;

        QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
        for (int i = 1; i < predicate.GetLength(0); i++)
        {
            QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
        }

        if (QueryList.FirstOrDefault() == null)
            throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
        return QueryList.FirstOrDefault();
    }
    }

Now the LambdaExpression Builder, it's a base one(with String type or something else) , you can improve it with more functionnality :

    private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
    {
        LambdaExpression lambda = null;

        Expression Criteria = null;

        Random r = new Random();
        ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());

        if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
        {
            Expression left = Expression.PropertyOrField(predParam, FieldName);
            Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
            //Type du champ recherché
            Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
            Expression right = Expression.Constant(FieldValue, propType);
            Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
            Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
        }
        else
        {
            Expression left = Expression.PropertyOrField(predParam, FieldName);
            Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
            Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);

            Criteria = Expression.Equal(left, right);
        }

        lambda = Expression.Lambda(Criteria, predParam);
        return lambda;
    }

Now the Calling function :

    public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
    {
        //Get the type
        Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
        Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
        //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
        ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
        IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });

        //Building the string type Expression<func<T,bool>> to init the array
        Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
        Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
        Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);

        MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });

        if (method == null)
            throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);

        int j = 0;
        IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
        criterias.Reset();
        while (criterias.MoveNext())
        {
            if (!String.IsNullOrEmpty(criterias.Key.ToString()))
            {
                lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
            }
            else
            {
                throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
            }
            j++;
        }

        Object item = method.Invoke(DalInstance, new object[] { lambda });
        }

The argument are : String Entity : Entity class name. XMLContext : it s the unit of work of the repository, argument i use to initialize the Model class Hashtable FieldsNameToGet : Index/value of the list of the field i want to get back Hashtable FieldFilter : the key/Value with FieldName/Content used to make the Lambda expression

Good Luck.

Alan
  • 9
  • 3