tags:

views:

497

answers:

3

Based on my question from yesterday: link text

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

+2  A: 

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
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
Nice solution Josh!
grenade
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.
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
Josh: my query returns IQueryable<Client> which i then send down to a pagination routine: public PaginatedList(IQueryable<T> 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)); }
+2  A: 

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
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]'.
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
@Jason: Too many users with the display name "Jason" in my opinion.
Jason
A: 

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

Vasu Balakrishnan