Question

I am a bit tired of writing lines of service layer codes like these:

Below codes just an example for readers. So they may have errors or typos, sorry about it :)

//ViewModel
public class EntityIndexFilterPartial
{
    public int? Id { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public IEnumerable<SelectListItem> StatusList { get; set; }
    public int? StatusID { get; set; }
}


//Service Layer method
//Method parameters are reperesents view model properties
public IQueryable<Entity> FilterBy(int? id, DateTime? startDate, DateTime? endDate, int? statusId)
{
    var filter = _db.Entities.AsQueryable();
    if (id.HasValue)
        filter = filter.Where(x => x.Id == id.Value);
    if (startDate.HasValue)
        filter = filter.Where(x => x.StartDate >= startDate.Value);
    if (endDate.HasValue)
        filter = filter.Where(x => x.EndDate <= endDate.Value);
    if (statusId.HasValue)
        filter = filter.Where(x => x.EntityStatus.StatusID == statusId);
    return filter;
}

I search pretty enough for some smart designed codes. I know about Dynamic LINQ library and I use it too. But I'm looking for strongly typed filtering. I don't want to write magic strings or some sort of them.

So basically I found some solutions but I want to hear from this community for some well written smart codes. Of course there may be dozens of solutions but again how would you write your strongly typed filtering service layer codes. Any ideas...

Here are few of my solutions:

Solution 1: Same FilterBy method but parameters are different, now taking expression list. So this means I'm creating predicateList in controller and send it to here.

public IQueryable<Entity> FilterBy(List<Expression<Func<Entity,bool>>> predicateList)
{
    var filter = _db.Entities.AsQueryable();
    foreach (var item in predicateList)
    {
        filter = filter.FilterBy(item);
    }
    return filter;
}

Solution 2: FilterBy method taking the EntityIndexFilterPartial as parameter in Application Service layer(not domain service). I'm sure this design has some problems, but I want to hear your opinions.

public IQueryable<Entity> FilterBy(EntityIndexFilterPartial filterPartial)
{
    //I'm calling the domain service layer FilterBy method such as like in solution 1.
}

Solution 3: I think this one much better than others but I'm still thinking for something more simple and better codes.

//helper class
public static class FilterByHelper
{
    public static IQueryable<T> If<T>(this IQueryable<T> filter, bool condition, Expression<Func<T, bool>> predicate)
    {
        if (condition)
            return filter.FilterBy(predicate);
        return filter;
    }

    public static IQueryable<T> FilterBy<T>(this IQueryable<T> filter, Expression<Func<T, bool>> predicate)
    {
        return filter.Where(predicate);
    }
}


public IQueryable<Entity> FilterBy(int? id, DateTime? startDate, DateTime? endDate, int? statusId)
{
    return _db.Entities
        .If(id.HasValue, x => x.Id == id.Value)
        .If(startDate.HasValue, x => x.StartDate >= startDate.Value)
        .If(endDate.HasValue, x => x.EndDate <= endDate.Value)
        .If(statusId.HasValue, x => x.EntityStatus.StatusID == statusId);
}

I know this became a bit long question but I hope I clearly ask what I want to ask.

As a quick and simple question do you know any smartly designed code for saving us from writing same lines of these filtering codes?

Btw, I'm not looking for desing pattern solutions or huge answers, you can give me some example or say how to find the better path is enough.

Of course If you write a full explained response I will be appricated.

Thank you.

Was it helpful?

Solution

Have you tried simple || conditions?

return _db.Entities
    .Where(x => id == null || x.Id == id.Value)
    .Where(x => startDate == null || x.StartDate >= startDate.Value)
    .Where(x => endDate == null ||  x.EndDate <= endDate.Value)
    .Where(x => statusId == null || x => x.EntityStatus.StatusID == statusId);

? I would expect that after query optimization, the no-op filters would be equivalent to not adding the filters at all.

OTHER TIPS

I solve this problem by using fluent extension methods. I feel this is a pretty nice solution to this particular class of problem. The nice thing about using a fluent style of defining your filters is that it produces some really readable code when you actually consume it. What this does in practice is it makes your service layer a bit more dynamic.

for example

If you were to have

public class User {
    public int Id {get;set;}
    public DateTime CreatedOn {get;set;}
    public string Name {get;set;}
    public DateTime BirthDate {get;set;}
}

you could write some filters similar to this

public IQueryable<User> OlderThan(this IQueryable<User> users, DateTime olderThan){/*implementation*/}

public IQueryable<User> CreatedAfter(this IQueryable<User> users, DateTime createdAfter){/*implementation*/}

//or something more generic
public IQueryable<User> WhereName(this IQueryable<User> users, Expression<Func<string,bool>> nameQuery){/*implementation*/}

and then chain these things togeather like so:

users
    .CreatedAfter(new DateTime(2011,1,1))
    .OlderThan(new DateTime(1985,1,2))
    .WhereName(n=>n.StartsWith("L"));

this allows your filtering logic to be a bit more dynamic without creating catchall style filters which are hard to maintain and complex

In practice this means

  • Filters can be very simple
  • you can create aggregation filters which apply another filter internally
  • Filters perform only a single purpose
  • they can have business relevant names

In my blog I talk about this method with some real examples. http://blog.staticvoid.co.nz/2013/2/25/a_case_for_generic_repositories

Hi Yusuf This post may be Helpfull for you .Solution looks smart,It implements dynamic where conditions to linq query and looks simple.

http://amitech.co/amitech-lab/item/dynamically-add-conditions-in-linq

Hope it helps

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top