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