Say that I have an abstract base class Shape
and its derived classes Triangle
, Square
, etc.
Currently I have the following (simplified) structure to fetch a list of Shape
s and filter them (mostly using Fluent):
public abstract class BaseSearch<T> where T : Shape
{
protected readonly IRepository<T> repository;
public IQueryable<T> Search(Filter filter)
{
var shapes = repository.GetAll();
// applies the common filtering to shapes
return CustomSearch(filter, shapes);
}
public abstract IQueryable<T> CustomSearch(Filter filter, IQueryable<T> shapes);
}
public class TriangleSearch : BaseSearch<Triangle>
{
public IQueryable<Triangle> CustomSearch(Filter filter, IQueryable<Triangle> shapes)
{
var filteredTriangles = shapes;
// applies filters specific to Triangles
return filteredTriangles;
}
}
The same goes for the rest of the derived class. They just need to implement the CustomSearch
method.
The problem is that now different places require different filters, with different conditions, properties, etc. Augmenting the Filter
class to handle those extra requirements would make it too cubersome.
To solve this problem, I though about having many filter classes, each of them would apply its simple filter algorithm to a list of Square
s. Then, using composition I could nest them all and have the whole filter I need.
The problem with this approach is that some properties lie in different places on each entity. For example, to filter by height, I need to compare the MiddleHeight
property from Triangle
and the Height
property from Square
. Also, neither of those properties are present in the Square
class.
I also thought about using the Predicate Builder approach, but I would fall into the same problem.
What could be used to implement a more general or dynamic filter that could be used to filter instances of classes derived from Shape
and still return an IQueryable
of shapes?
Edit: explaining further the filter class and how its used.
public class Filter {
public string Color { get; set; }
}
public class AnotherFilter {
public Filter MainFilter { get; set; }
public long? Height { get; set; }
}
The main search uses the Filter
class. It checks if the Color
property is set and if so, applies a condition to the collection of shapes. This condition is not always a simple "where the color of the shape matches the color set in the filter"; sometimes the property requires a more complex check (e.g. "where the color of the shape matches that of the filter and the shape has the opacity set to 0%").
The AnotherFilter
class represents a different filter. Currently it has a Filter
property which is used to call the main Search
and fetch some results. Then, using the properties of AnotherFilter
, the collection is filtered further. There is also a problem here mentioned before: the Height
property is to be used to filter data by height. But Height
is not a property of Shape
, only of, say, Triangle
and Square
(a Circle
, which also inherits from Shape
, doesn't have a height).
The search fetches data from a remote database and uses NHibernate.
Best Answer
Use Dynamic Linq.
Dynamic linq will allow you to write "stringly-typed" queries with conditional statements that can be created and resolved at run time, rather than at compile time.
Further Reading
Getting Started with Dynamic Linq