3
\$\begingroup\$

I have various types of EF entities, all of them have a navigation property called "Employee". When generating reports the user will have the option to filter the report according to the different employee properties (Cost Center, gender, etc.).

Something like this:

var courses = context.Courses
              .Where(c => c.Employee.CostCenterID == ccID
                     && c.Employee.Rank == rankID
                     ....
                    )
                    .ToList();

The real filter code is much more longer but this was just a hint. Anyway, I figured out an easier way of doing this filtering with an extension method using an interface:

public interface IFilterable
{
    Employee Employee
    {
        get;
        set;
    }
}

Then, I added partial classes to inherit the previous interface for different entities that has the Employee navigation property, for example:

public partial class Course: IFilterable
{
}

Then created the following generic method:

public static IQueryable<T> Filter<T>(this IQueryable<T> source, SearchCriteria sc) 
    where T : class, IFilterable
{
    var filtered = source.Where(e => e.Employee.CostCenterID == sc.CostCenterID 
        && e.Employee.Gender == sc.Gender
        .....
        );

     return filtered;
}

then simply I can use it like this on any class that inherits IFilterable:

var list = context.Courses.Filter(sc).ToList();

Note: the SearchCriteria is just a simple class that holds different employee properties.

Q: Is this the way to do it? or is there a more elegant way?

\$\endgroup\$
1
\$\begingroup\$

I think you might run into some design ugliness with the in the future because you are now coupling the knowledge that an entity could be filtered by a certain property to each entity.

Imagine a requirement comes in to filter by Course - what would that look like in your design?

Are you going to add a Course property to your IFilterable - but then what if an entity currently implementing IFilterable doesn't have a Course? So you then might need to add another IFilterable interface like an ICourseFilterable and another Filter method to go with it. And then if you want to filter on another entity ... seems quite cumbersome and involved to me.

I think the simplest and most flexible way forward is to actually move the matching responsibility into the SearchCriteria. So instead of having a SearchCriteria you rename it to EmployeeSearchCriteria and give it a method to match a particular employee:

public class EmployeeSearchCriteria
{
    private int _CostCenterId;
    private Gender _Gender;
    ... // other fields to match

    public EmployeeSearchCriteria(int costCenterId, Gender gender, ...)
    {
        _CostCenterId = costCenterId;
        _Gender = gender;
        ...
    }

    public bool Matches(Employee e)
    {
        return e.CostCenterID == _CostCenterId && e.Gender == _Gender && ... ;
    }
}

Then you can do your filtering via:

var list = context.Courses.Where(c => sc.Matches(c.Employee)).ToList();

And in the future if you want to filter entities by Course you create a CourseSearchCriteria with a method bool Matches(Course c) which you then can use as

var list = context.Employees.Where(e => sc.Matches(e.Course)).ToList();

The advantage is that the filtering by specific entities is encapsulated into single classes with single responsibilities and you do not have to litter your code with all these filter interfaces.

The concern that an entity can be filtered is now removed from the entity and kept separate which is always a good thing. The more an entity has to know what might happen to it the more complex your code structure will become.

\$\endgroup\$
  • \$\begingroup\$ Good idea, there is one problem I think. I am querying against a database directly, hence the Matches method can not be translated to SQL, which will force me to load the whole table to be able to use Linq-to-Objects in order to achieve this style, Am I right here? \$\endgroup\$ – Nean Der Thal Jan 29 '14 at 20:58
  • \$\begingroup\$ @MIH, hmm yes I think you are correct, have to think about that one \$\endgroup\$ – ChrisWue Jan 30 '14 at 2:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.