How to create an Expression builder in .NET

  • A+
Category:Languages

I have written some code to allow filtering of products on our website, and I am getting a pretty bad code smell. The user can select 1-* of these filters which means I need to be specific with the WHERE clause.

I think I am looking for a way to build up a lambda expression, so for every filter I can 'modify' my WHERE clause - but I am not sure how to do this in .NET, and there must be a way.

Code in its current state (effectively hardcoded, not dynamic, would be a pain to add more filter options).

public static class AgeGroups {     public static Dictionary<string, int> Items = new Dictionary<string, int>(){         { "Modern (Less than 10 years old)", 1 },         { "Retro (10 - 20 years old)", 2 },         { "Vintage(20 - 70 years old)", 3 },         { "Antique(70+ years old)", 4 }     };      public static IQueryable<ProductDTO> FilterAgeByGroup(IQueryable<ProductDTO> query, List<string> filters)     {         var values = new List<int>();         var currentYear = DateTime.UtcNow.Year;         foreach (var key in filters)         {             var matchingValue = Items.TryGetValue(key, out int value);              if (matchingValue)             {                 values.Add(value);             }         }          if (Utility.EqualsIgnoringOrder(values, new List<int> { 1 }))         {             query = query.Where(x => x.YearManufactured >= currentYear - 10);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 2 }))         {             query = query.Where(x => x.YearManufactured <= currentYear - 10 && x.YearManufactured >= currentYear - 20);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 3 }))         {             query = query.Where(x => x.YearManufactured <= currentYear - 20 && x.YearManufactured >= currentYear - 70);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 4 }))         {             query = query.Where(x => x.YearManufactured <= currentYear - 70);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 1, 2}))         {             query = query.Where(x => x.YearManufactured >= currentYear - 20);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 1, 3 }))         {             query = query.Where(x => x.YearManufactured >= currentYear - 10 || (x.YearManufactured <= currentYear - 20 && x.YearManufactured >= currentYear - 70));         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 1, 4 }))         {             query = query.Where(x => x.YearManufactured >= currentYear - 10 ||  x.YearManufactured <= currentYear - 70);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 2, 3 }))         {             query = query.Where(x => x.YearManufactured <= currentYear - 10 && x.YearManufactured >= currentYear - 70);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 2, 4 }))         {             query = query.Where(x => (x.YearManufactured <= currentYear - 10 && x.YearManufactured >= currentYear - 20)                                       || x.YearManufactured <= currentYear - 70);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 1, 2, 3 }))         {             query = query.Where(x => x.YearManufactured >= currentYear - 70);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 1, 2, 4 }))         {             query = query.Where(x => x.YearManufactured >= currentYear - 20 || x.YearManufactured <= currentYear - 70);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 2, 3, 4}))         {             query = query.Where(x => x.YearManufactured <= currentYear - 10);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 1, 3, 4}))         {             query = query.Where(x => x.YearManufactured >= currentYear - 10 || x.YearManufactured <= 20);         }         else if (Utility.EqualsIgnoringOrder(values, new List<int> { 1, 2, 3, 4 }))         {             // all         }         return query;     } } 

 


I've recently ran into this issue myself. Through the help of another question on SO I found http://www.albahari.com/nutshell/predicatebuilder.aspx. Basically you want to build a predicate and pass that into the where clause of your query.

public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> where1,       Expression<Func<T, bool>> where2) {     InvocationExpression invocationExpression = Expression.Invoke(where2,           where1.Parameters.Cast<Expression>());     return Expression.Lambda<Func<T, bool>>(Expression.OrElse(where1.Body,           invocationExpression), where1.Parameters); }  public static IQueryable<ProductDTO> FilterAgeByGroup(IQueryable<ProductDTO> query,      List<string> filters, int currentYear) {     var values = new HashSet<int>();     //Default value     Expression<Func<ProductDTO, bool>> predicate = (ProductDTO) => false;      foreach (var key in filters)     {         var matchingValue = Items.TryGetValue(key, out int value);          if (matchingValue)         {             values.Add(value);         }     }      if (values.Count == 0)         return query;      if (values.Contains(1))     {         predicate = predicate.Or(x => x.YearManufactured >= currentYear - 10);     }      if (values.Contains(2))     {         predicate = predicate.Or(x => x.YearManufactured <= currentYear - 10 &&              x.YearManufactured >= currentYear - 20);     }      if (values.Contains(3))     {         predicate = predicate.Or(x => x.YearManufactured <= currentYear - 20 &&              x.YearManufactured >= currentYear - 70);     }      if (values.Contains(4))     {         predicate = predicate.Or(x => x.YearManufactured <= currentYear - 70);     }      return query.Where(predicate); } 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: