views:

992

answers:

6

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  A: 

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...

Marc Gravell
The predicate form is exactly what I'd have posted. It's so flexible, and with lambda expressions it's just as concise as any other way.
Jon Skeet
Indeed; and by being Expression<...> it can be compiled to use with object-based repositories. The downside is that there is still a risk of LOLA re the unsupported query operations...
Marc Gravell
In *theory* I like the IQueryable<T> (return) option, but it makes it hard to isolate DAL from UI (i.e. guarantee when data access starts and ends) - but it is still a viable option for some secnarios.
Marc Gravell
Any reason why you would go for IList<T> as a return type instead of IEnumerable<T> ?
Peter Morris
For illustration, I just wanted the repository to clearly finishing executing before passing data back. Both pre-fetched and streaming approaches are valid, though.
Marc Gravell
A: 

I am answering this based on my little knowledge of patterns.

Decorator pattern might help here (considering you can add a filter & get results. Apply new filters on it & get new results)

shahkalpesh
+1  A: 

I think I'd make a Category class and a Store class, instead of just strings:

class Category
{
  public Category(string s)
  {
    ...
  }
  ...
}

And then maybe:

Product.GetProducts(
  Category category, //if this is null then don't filter on category
  Store store //if this is null then don't filter on store
  )
{
  ...
}

The Category and Store classes might be related (they might both be subclasses of a Filter class).

ChrisW
A: 

I would go with something like a strategy for the filters themselves and write CategoryFilter and StoreFilter classes. Then I would use a composite or decorator to combine the filters.

Yordan Pavlov
+6  A: 

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 :-)

Peter Morris
could you show me an example of combining LINQ expressions?
Added an example for you, take a look and see how you get on.
Peter Morris
A: 

Can't you just add Where stuff as you go here?

var products = datacontext.Products;

if(!String.IsNullOrEmpty(type))
  products = products.Where(p => p.Type == type);

if(!String.IsNullOrEmpty(store))
  products = products.Where(p => p.Store == store);

foreach(var p in products)
  // Do whatever

or something like that...

Svish