Atmane EL BOUACHRI Atmane EL BOUACHRI - 2 months ago 13
C# Question

Factoring WHERE clauses in IQueryable

I recovered some spaghetti code and I have to refactor it. I do not want a method with over 200 lines, for me it is not object oriented programming . I am trying to ponder on the question, I would like to have your suggestions.

This is my code:

Line 18

if (searchCriteria.EventReference != null)
{
query = query.Search(x = > x.EventReference, searchCriteria.EventReference);
}
if (searchCriteria.PendingEvent == false)
{
query = query.Where(x = > x.EventStatusId != EventStatus.Pending);
}
if (searchCriteria.VerifiedEvent == false)
{
query = query.Where(x = > x.EventStatusId != EventStatus.Verified);
}
if (searchCriteria.CanceledEvent == false)
{
query = query.Where(x = > x.EventStatusId != EventStatus.Canceled);
}


Line 237

if (searchCriteria.RemitterId != null)
{
query = query.Where(x = > x.Trade.RemitterId == searchCriteria.RemitterId);
}

Answer

This one seems to be overkill to me (but I guess it's the polymorphism that appears in the comments), but anyway, there it is:

We start with an interface:

public interface IQueryFilter
{
    IQueryable<Whatever> Filter(IQueryable<Whatever> query, SearchCriteria searchCriteria);
}

Then implement the common property:

public abstract class AQueryFilter<T> : IQueryFilter
{
    public AQueryFilter(Func<SearchCriteria, T> criteria)
    {
        Criteria = criteria;
    }

    protected Func<SearchCriteria, T> Criteria { get; }
    public abstract IQueryable<Whatever> Filter(IQueryable<Whatever> query, SearchCriteria searchCriteria);
}

And finally, all the specific stuff:

public class WhereEventStatusQueryFilter : AQueryFilter<bool>
{
    private EventStatus _toTest;

    public WhereEventStatusQueryFilter(Func<SearchCriteria, bool> criteria, EventStatus toTest)
        : base(criteria)
    {
        _toTest = toTest;
    }

    public override IQueryable<Whatever> Filter(IQueryable<Whatever> query, SearchCriteria searchCriteria)
    {
        return (Criteria(searchCriteria) ? query : query.Where(x => x.EventStatusId != _toTest));
    }
}

public class SearchQueryFilter : AQueryFilter<object>
{
    Func<Whatever, object> _searchFor;

    public SearchQueryFilter(Func<SearchCriteria, object> criteria, Func<Whatever, object> searchFor)
        : base(criteria)
    {
        _searchFor = searchFor;
    }

    public override IQueryable<Whatever> Filter(IQueryable<Whatever> query, SearchCriteria searchCriteria)
    {
        return (Criteria(searchCriteria) == null ? query : query.Search(x => _searchFor(x), Criteria(searchCriteria)));
    }
}

public class WhereEqualQueryFilter : AQueryFilter<object>
{
    Func<Whatever, object> _searchFor;

    public WhereEqualQueryFilter(Func<SearchCriteria, object> criteria, Func<Whatever, object> searchFor)
        : base(criteria)
    {
        _searchFor = searchFor;
    }

    public override IQueryable<Whatever> Filter(IQueryable<Whatever> query, SearchCriteria searchCriteria)
    {
        return (Criteria(searchCriteria) == null ? query : query.Where(x => _searchFor(x) == Criteria(searchCriteria)));
    }
}

Usage:

var filters = new IQueryFilter[]
{
    new WhereEventStatusQueryFilter(x => x.PendingEvent, EventStatus.Pending),
    new WhereEventStatusQueryFilter(x => x.VerifiedEvent, EventStatus.Verified),
    new SearchQueryFilter(x => x.EventReference, x => x.EventReference),
    new WhereEqualQueryFilter(x => x.RemittedId, x => x.Trade.RemittedId),
    ...
};

foreach (var filter in filters)
    query = filter.Filter(query, searchCriteria);

But this solution hide a lot of the logic. And if anyone wants to add something he has to read all the previous filter classes to know if there is already one that can get the job done or if he has to write another one.