Search code examples
c#ienumerableexpression-treesiqueryablefunc

How to convent viewmodel to Expression<Func<T,bool>>?


Piggybacking off of a very similar question...

I need to generate an Expression from a ViewModel to pass as a search predicate for IQueryable.Where. I need to be able to include/exclude query parameters based on what is provided by the user. Example:

public class StoresFilter
{
    public int[] Ids { get; set; }

    [StringLength(150)]
    public string Name { get; set; }

    [StringLength(5)]
    public string Abbreviation { get; set; }

    [Display(Name = "Show all")]
    public bool ShowAll { get; set; } = true;

    public Expression<Func<Store, bool>> ToExpression()
    {
        List<Expression<Func<Store, bool>>> expressions = new List<Expression<Func<Store, bool>>>();

        if (Ids != null && Ids.Length > 0)
        {
            expressions.Add(x => Ids.Contains(x.Id));
        }
        if (Name.HasValue())
        {
            expressions.Add(x => x.Name.Contains(Name));
        }
        if (Abbreviation.HasValue())
        {
            expressions.Add(x => x.Abbreviation.Contains(Abbreviation));
        }
        if (!ShowAll)
        {
            expressions.Add(x => x.Enabled == true);
        }
        if (expressions.Count == 0)
        {
            return x => true;
        }

        // how to combine list of expressions into composite expression???
        return compositeExpression;
    }
}

Is there a simple way to build a composite expression from a list of expressions? Or do I need to go through the process of manually building out the expression using ParameterExpression, Expression.AndAlso, ExpressionVisitor, etc?


Solution

  • You should not build and combine Expressions, but instead of this you should do it through IQuerable<Store> via .Where chain. Moreover, source.Expression will contain desired expression:

    public IQueryable<Store> ApplyFilter(IQueryable<Store> source)
    {
        if (Ids != null && Ids.Length > 0)  
            source = source.Where(x => Ids.Contains(x.Id)); 
    
        if (Name.HasValue())    
            source = source.Where(x => x.Name.Contains(Name));  
    
        if (Abbreviation.HasValue())    
            source = source.Where(x => x.Abbreviation.Contains(Abbreviation));  
    
        if (!ShowAll)   
            source = source.Where(x => x.Enabled == true);      
    
        //or return source.Expression as you wanted
        return source;
    }
    

    Usage:

    var filter = new StoresFilter { Name = "Market" };
    var filteredStores = filter.ApplyFilter(context.Stores).ToList();