1

I have an MVC controller that will filter a product list based on a category.

Products = repository.Products.Where(p => category == null || p.Category1 == "category1" );

If I wanted to let the user filter the product with two categories, I would have to add in another if statement that contains Category1 and Category2. I can imagine if I have more categories, and the user can choose category 1,3,5 and so on, the permutation will get crazily large.

Is there a proper way of doing this?

tereško
  • 58,060
  • 25
  • 98
  • 150
jingchyu
  • 161
  • 1
  • 8
  • you may need to build lambda expression...see this link it might be a good start http://stackoverflow.com/questions/16240630/dynamically-add-new-lambda-expressions-to-create-a-filter – Dan Hunex Aug 26 '13 at 23:54

3 Answers3

1

I am assuming that your object model is defined along the lines of:

public class Product
{
    // ...
    public Category Category1 { get; set; }
    public Category Category2 { get; set; }
    public Category Category3 { get; set; }
    // ...
}

(where you might be using strings instead of having a category class)

If the object model is within your control, then I would recommend changing it so that a product has a collection of categories rather than several named properties for Category1, Category2, Category3 etc, so more like this:

public class Product
{
    // ...
    public IList<Category> Categories { get; set; }
    // ...
}

If the product class is fixed and it already has multiple individual category properties, I would recommend writing an extension method for your product class that returns a list of categories that are non-null. That way you can write a where expression more succinctly.

For example:

public static class ProductExtension
{
    public static IList<Category> GetCategories(this Product product)
    {
        List<Category> categories = new List<Category>();

        if (product.Category1 != null)
        {
            categories.Add(product.Category1);
        }
        if (product.Category2 != null)
        {
            categories.Add(product.Category2);
        }
        // etc.

        return categories;
    }
}

...which could then be used along the lines of

repository.Products.Where(p => p.GetCategories().Contains("category1"));
Stephen Hewlett
  • 2,415
  • 1
  • 18
  • 31
  • Can you slightly elaborate on the first case? Do you mean creating a new class called Categories and create a member instance in my Product Class? – jingchyu Aug 27 '13 at 00:08
1

Another option is to create a ProductFilter object to do the filtering for you.

Give the ProductFilter class a field for every category that is possible to filter on, which each store predicates, and a PassesFilter(Product p) method which determines whether p passes the predicate for all categories where a predicate has been set, e.g.

method PassesFilter(Product p):
   if Category1Filter is not null:
       if p does not pass Category1Filter:
           return false
    if Category2Filter is not null:
       if p does not pass Category2Filter:
           return false
    return true

(Excuse the pseudo-code, I don't do C# and it's late)

So you could use it like so:

ProductFilter pf = new ProductFilter();
...
/*build up your filters for all categories that apply in this search...*/
pf.ColourFilter = (Product p) => { return p.Colour == "Red"; };
pf.PriceFilter = (Product p) => { return p.Price > 100.00; };
...
Products = repository.Products.Where(p => category == null || pf.PassesFilter(p) );

You could also easily implement the PassesFilter method differently to handle OR instead of AND (or create a class for each implementation).

I know that using predicates in the way I described would allow you to put a price predicate in the colour predicate field, but I just thought I'd throw this example out there to illustrate the concept of using an object to do the work of lambdas :-)

andyhasit
  • 14,137
  • 7
  • 49
  • 51
0

1.You may use Expression to constructor condition expression

2.Use expression in the linq.

user2492798
  • 589
  • 1
  • 4
  • 8