Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter IEnumerable based on an entity input parameter

I'm using now Entity framework- but it's a problem "shared" between all ORM's and even IEnumerable.

Let's say I have a method in MVC looks like this:

[HttpPost]
public ActionResult Foo(FooModel model)
{
    var context = new Context(); -- The EF session
    var data = context.Foo.Where(???).ToList();
    return View(data);
}

I want to query the context based on the input parameter like:

var data = context.Foo.Where(x => x.Date == model.Date &&
                             x.Name == model.Name &&
                             x.ItemCode = model.ItemCode).ToList();

But it's more complicated than that, because if one of the parameters above(Date\ Name\ ItemCode) is null I don't want to include it inside the query.
If I hard code it can looks similar to this:

var query =  context.Foo;

if (model.Date != null)
    query =query.Where(x => x.Date == model.Date);

if (model.ItemCode != null)
    query =query.Where(x => x.ItemCode == model.ItemCode);
...

There must be a simpler way than this.
I need a way to generate an expression of the type Expression<T, bool> to be used in the Where method.

[HttpPost]
public ActionResult Foo(FooModel model)
{
    var context = new Context(); -- The EF session
    var data = context.Foo.Where(THE_EXPRESSION).ToList();
    return View(data);
}

Is there a built-in way to build that expression? Is there a package in nuget that does it?


Update: There could be more than 30 properites in the model-entity; writing 30 times the Where for each query can be a pain in the neck:

.Where(model.Date != null, x => x.Date == model.Date)
.Where(model.Name != null, x => x.Name == model.Name)
.Where(model.ItemCode != null, x => x.ItemCode == model.ItemCode)
...
...
...
.ToList();
like image 788
gdoron is supporting Monica Avatar asked Sep 13 '12 10:09

gdoron is supporting Monica


3 Answers

Try that. This is using reflection and expressions to build the query dynamically. I tested it only with objects.

static IQueryable<T> Filter<T>(IQueryable<T> col, T filter)
{
    foreach (var pi in typeof(T).GetProperties())
    {
        if (pi.GetValue(filter) != null)
        {
            var param = Expression.Parameter(typeof(T), "t");
            var body = Expression.Equal(
                Expression.PropertyOrField(param, pi.Name),
                Expression.PropertyOrField(Expression.Constant(filter), pi.Name));
            var lambda = Expression.Lambda<Func<T, bool>>(body, param);
            col = col.Where(lambda);
        }
    }

    return col;
}
like image 140
Amiram Korach Avatar answered Nov 03 '22 11:11

Amiram Korach


Your hard coded method is the best method generally.

However you can try to make your life a little easier by writing an appropriate extension method to help keep the code clean.

Try this for example:

public static class QueryableEx
{
    public static IQueryable<T> Where<T>(
        this IQueryable<T> @this,
        bool condition,
        Expression<Func<T, bool>> @where)
    {
        return condition ? @this.Where(@where) : @this;
    }
}

Now you could write this code:

[HttpPost]
public ActionResult Foo(FooModel model)
{
    using (var context = new Context())
    {
        var data = context.Foo
            .Where(model.Date != null, x => x.Date == model.Date)
            .Where(model.Name != null, x => x.Name == model.Name)
            .Where(model.ItemCode != null, x => x.ItemCode == model.ItemCode)
            .ToList();
        return View(data);
    }
}

(Please don't forget to dispose of your context or use using to do it for you.)

like image 37
Enigmativity Avatar answered Nov 03 '22 11:11

Enigmativity


I think you should encapsulate your logic into your Foo entity, e.g.

   public Foo
   {
     public bool isMatch(Model model)
     {
       // check your rules and return result
     }
   }

and use it in linq. Or look at Specification pattern

like image 40
syned Avatar answered Nov 03 '22 11:11

syned