which design pattern to use for filtering query? c#


Question

I have a database table with a list of products (clothing). The products belong to categories and are from different stores.

Sample categories: tops, bottoms, shoes

Sample stores: gap.com, macys.com, target.com

My customers can request to filter products in the following ways:

  • all products (no filter)
  • by category
  • by store
  • by category and store

Right now I have ONE method in my "Products" class that returns the products depending on the type of filter requested by the user. I use a FilterBy enum to determine which products need to be returned.

For example, if the user wants to view all products in the "tops" category I call this function:

Products.GetProducts(FilterBy.Category, "tops", ""); 

I have the last parameter empty because it's the string that contains the "store" to filter by but in this case there is no store. However, if the user wants to filter by category AND store I'd call the method this way:

Product.GetProducts(FilterBy.CategoryAndStore, "tops", "macys.com");

My question is, what's a better way to do this? I just learned about the strategy design pattern. Could I use that to do this in a better (easier to extend and easier to maintain) way?

The reason I'm asking this question is because I figure this must be a pretty common problem that people are repeatedly solving (filtering products in various ways)

2
10
2/8/2009 7:55:33 AM

Accepted Answer

According to Eric Evan's "Domain Drive Design" you need the specification pattern. Something like this

public interface ISpecification<T>
{
  bool Matches(T instance);
  string GetSql();
}

public class ProductCategoryNameSpecification : ISpecification<Product>
{
  readonly string CategoryName;
  public ProductCategoryNameSpecification(string categoryName)
  {
    CategoryName = categoryName;
  }

  public bool Matches(Product instance)
  {
    return instance.Category.Name == CategoryName;
  }

  public string GetSql()
  {
    return "CategoryName like '" + { escaped CategoryName } + "'";
  }
}

Your repository can now be called with specifications

var specifications = new List<ISpecification<Product>>();
specifications.Add(
 new ProductCategoryNameSpecification("Tops"));
specifications.Add(
 new ProductColorSpecification("Blue"));

var products = ProductRepository.GetBySpecifications(specifications);

You could also create a generic CompositeSpecification class which would contain sub specifications and an indicator as to which logical operator to apply to them AND/OR

I'd be more inclined to combine LINQ expressions though.

Update - Example of LINQ at runtime

var product = Expression.Parameter(typeof(Product), "product");
var categoryNameExpression = Expression.Equal(
  Expression.Property(product, "CategoryName"),
  Expression.Constant("Tops"));

You can add an "and" like so

var colorExpression = Expression.Equal(
  Expression.Property(product, "Color"),
  Expression.Constant("Red"));
var andExpression = Expression.And(categoryNameExpression, colorExpression);

Finally you can convert this expression into a predicate and then execute it...

var predicate = 
  (Func<Product, bool>)Expression.Lambda(andExpression, product).Compile();
var query = Enumerable.Where(YourDataContext.Products, predicate);

foreach(Product currentProduct in query)
  meh(currentProduct);

Probably wont compile because I have typed it directly into the browser, but I believe it is generally correct.

Another update :-)

List<Product> products = new List<Product>();
products.Add(new Product { CategoryName = "Tops", Color = "Red" });
products.Add(new Product { CategoryName = "Tops", Color = "Gree" });
products.Add(new Product { CategoryName = "Trousers", Color = "Red" });
var query = (IEnumerable<Product>)products;
query = query.Where(p => p.CategoryName == "Tops");
query = query.Where(p => p.Color == "Red");
foreach (Product p in query)
    Console.WriteLine(p.CategoryName + " / " + p.Color);
Console.ReadLine();

In this case you would be evaluating in memory because the source is a List, but if your source was a data context that supported Linq2SQL for example I think this would evaluate using SQL.

You could still use the Specification pattern in order to make your concepts explicit.

public class Specification<T>
{
  IEnumerable<T> AppendToQuery(IEnumerable<T> query);
}

The main difference between the two approaches is that the latter builds a known query based on explicit properties, whereas the first one could be used to build a query of any structure (such as building a query entirely from XML for example.)

This should be enough to get you started :-)

13
2/8/2009 6:55:10 PM

Exper Answer

The strategy pattern doesn't necessarily knit well with the common interface-based repository approach. Personally, I'd probably go one of two ways here:

  • One search method that supports combinations of options:

    IList<Product> GetProducts(string category, string store, ...);

(then selectively apply the combinations of filters (i.e. null means "any") - either when building a command, or pass down to a SPROC that does something similar.

  • With LINQ, maybe a predicate expression?

    IList<Product> GetProducts(Expression<Func<Product,bool>> predicate);

Of course, with LINQ you could also use composition by the caller, but that is harder to write a closed / fully-tested repository for:

 `IQueryable<Product> Products {get;}`

(and have the caller use .Where(x=>x.Category == "foo")) - I'm not so sure about this last one long-term...

2
2/8/2009 8:04:56 AM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon