Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strongly typed Linq filtering method

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.

like image 229
Yusuf Uzun Avatar asked Aug 02 '13 21:08

Yusuf Uzun


1 Answers

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.

like image 181
Jon Skeet Avatar answered Sep 20 '22 11:09

Jon Skeet