Search code examples
c#linqentity-framework-coreexpression-trees

Is there any trick to avoid double checks when constructing Where criteria conditionally?


I am wondering whether I can simplify my code below, especially the following 2 statements:

// Statement A
Expression<Func<Product, bool>>? criteria = p =>
    (id == null ? true : p.Id == id) &&
    (name == null ? true : p.Name.ToLower().Contains(name.ToLower())) &&
    (maxPrice == null ? true : p.Price <= maxPrice);


// Statement B
if (id is null && name is null && maxPrice is null)
    criteria = null;

I am not happy with the statement B because of two reasons:

  • I have to check again each parameter that has been checked in the statement A.
  • It is prone to error (forgetting to check all parameters)

Attempt

The following does not compile because

An expression tree cannot contain an assignment operator.

bool sign = false;
Criteria = p =>
    (id == null ? (sign = true) : p.Id == id) &&
    (name == null ? (sign = true) : p.Name.ToLower().Contains(name.ToLower()))
;

if (sign == false)
    Criteria = null;

Question

Is there any way to make it much simpler and less prone to errors? Any suggestions are always welcome.

static IQueryable<Product> Filter(this IQueryable<Product> products,
    int? id = null,
    string? name = null,
    decimal? maxPrice = null)
{
    Expression<Func<Product, bool>>? criteria = p =>
        (id == null ? true : p.Id == id) &&
        (name == null ? true : p.Name.ToLower().Contains(name.ToLower())) &&
        (maxPrice == null ? true : p.Price <= maxPrice);


    if (id is null && name is null && maxPrice is null)
        criteria = null;

    if (criteria is null)
        return products;
    else
        return products.Where(criteria);
}

Solution

  • Just follow the pretty standard pattern of dynamically adding Where based on presence of filtering conditions:

    static IQueryable<Product> Filter(this IQueryable<Product> products,
        int? id = null,
        string? name = null,
        decimal? maxPrice = null)
    {
        if (id is not null)
        {
            products = products.Where(p => p.Id == id);
        }
    
        if (name is not null)
        {
            products = products.Where(p => p.Name.ToLower().Contains(name.ToLower()));
        }
    
        if (maxPrice is not null)
        {
            products = products.Where(p => p.Price <= maxPrice);
        }
    
        return products;
    }