1

I need to be able to create LINQ's Where conditions/filters dynamically from a controller. Then, I want to pass these filters to a repository's method that will query the database after applying the dynamic filters using LINQ's Where extension.

Here is the code that I execute in my controller which dynamically creates filters using the IQueryable object

IQueryable<StageModel> stage = null;

if(model.ClientId != null)
{
    stage = stage.Where(s => s.ClientId == model.ClientId);
}

if (model.CategoryIds != null && model.CategoryIds.Any())
{
    var stageIds = new List<int>{ 1, 2, 3 }; // this will be dynamically generated

    stage = stage.Where(s => stageIds.Contains(s.Id));
}

Stages = unitOfWork.Stages.GetStagesPagedList(1, PerPage, stage.Expression as MethodCallExpression);

... 
...

Finally, in my repository I have this method that takes Expression<Func<StageModel, bool>> expression in the third argument and passes it to the Where extension if it isn't null.

public IPagedList<StageModel> GetStagesPagedList(int pageNumber, int pageSize, Expression<Func<StageModel, bool>> predicate = null)
{
    IQueryable<StageModel> stages = CastedContext.Stages;

    if (predicate != null)
     {
         stages = stages.Where(predicate);
     }

     return stages.OrderBy(stage => stage.Name)
                  .ToPagedList(pageNumber, pageSize);

}

But I am getting an error on the following line

unitOfWork.Stages.GetStagesPagedList(1, PerPage, stage.Expression as MethodCallExpression)

This is what the error shows

Error 3 Argument 3: cannot convert from 'System.Linq.Expressions.MethodCallExpression' to 'System.Linq.Expressions.Expression>'

I also tried not casing the expression like so unitOfWork.Stages.GetStagesPagedList(1, PerPage, stage.Expression)

Error 3 Argument 3: cannot convert from 'System.Linq.Expressions.Expression' to 'System.Linq.Expressions.Expression>'

How can I correctly do the conversion? Is this isn't possible, how can I dynamically create filters and pass them to my repository?

Junior
  • 11,602
  • 27
  • 106
  • 212
  • How do you construct your stage.Expression? It shouldn't be MethodCallExpression, it should be LambdaExpression i guess – Maksim Simkin Jan 20 '17 at 20:26
  • Why don't you add expressions to `unitOfWork.Stages` instead of `stage`? – Gert Arnold Jan 20 '17 at 20:32
  • @MaksimSimkin I construct my method as you see in my code. I construct it in the controller and then I pass it to the `GetStagesPagedList` method – Junior Jan 20 '17 at 20:38

3 Answers3

2

Simply have GetStagesPagedList accept the IQueryable that you have, rather than an Expression.

public IPagedList<StageModel> GetStagesPagedList(IQueryable<StageModel> stages, 
    int pageNumber, int pageSize)
{
     return stages.OrderBy(stage => stage.Name)
         .ToPagedList(pageNumber, pageSize);
}

I don't know that that's doing enough to really warrant another method, but you're free to use it if you want.

Also your caller has a major bug in that you initialize the IQueryable to null, when you need to initialize it to the data context's table:

IQueryable<StageModel> stages = CastedContext.Stages;

//...

Stages = unitOfWork.Stages.GetStagesPagedList(stages, 1, PerPage);
Servy
  • 202,030
  • 26
  • 332
  • 449
  • The caller does not have access to `CastedContext.Stages` which is why I am not initializing it with `CastedContext.Stages` However, in the `GetStagesPagedList` method, I check if the argument is null or not. I am not sure if I am missing something else – Junior Jan 20 '17 at 21:06
  • @MikeA Your code is going to throw a null argument exception any time you call `Where` because you're calling `Where` on a `null` queryable. That will just break. You need to actually have the base queryable from the data source in order to construct a query. I'd *strongly* consider re-designing your solution such that you simply have access to it `(or an `IQueryable` provided through some layers of indirection) when constructing your query. – Servy Jan 20 '17 at 21:09
  • Are you saying I should have a getter method that returns the `CastedContext.Stages` then use that method to build `IQuerable` object and then pass it as an argument? – Junior Jan 20 '17 at 21:13
  • @MikeA It's hard to say without knowing more about the design of your application. All I can say is that you should have an `iQueryable` in whatever location is responsible for constructing the query. How you can best provide it to that location is beyond what I have enough information to comment on. – Servy Jan 20 '17 at 21:15
1

Using fake IQueryable to build predicate is not a good idea. The chained Where technique is applicable when you have the actual IQueryable. In order to build predicate expression, all you need is some predicate builder helper utility.

For instance, you can take my own PredicateUtils class from Establish a link between two lists in linq to entities where clause. It perfectly fits because handles null predicates.

Copy/paste the class to your project, then use something like this (basically replace stage = stage.Where with predicate = predicate.And):

var predicate = PredicateUtils.Null<StageModel>();

if(model.ClientId != null)
{
    predicate = predicate.And(s => s.ClientId == model.ClientId);
}

if (model.CategoryIds != null && model.CategoryIds.Any())
{
    var stageIds = new List<int>{ 1, 2, 3 }; // this will be dynamically generated

    predicate = predicate.And(s => stageIds.Contains(s.Id));
}

Stages = unitOfWork.Stages.GetStagesPagedList(1, PerPage, predicate);

... 
...
Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
0

You need to manually create an Expression like this:

var parameter = Expression.Parameter(typeof(StagedModel), "s");
Expression stage = null;
if (model.ClientId != null)
{
    stage = Expression.Equal(Expression.PropertyOrField(parameter, "ClientId"), Expression.Constant(model.ClientId));
}
if (model.CategoryIds != null && model.CategoryIds.Any())
{
    var stageIds = new List<int> { 1, 2, 3 };    
    Expression contains = null;
    foreach (var id in stageIds)
    {
        var equals = Expression.Equal(Expression.Constant(id), Expression.PropertyOrField(parameter, "Id"));
        contains = contains == null ? equals : Expression.OrElse(contains, equals);
    }

    stage = stage == null ? stage : Expression.AndAlso(stage, contains);
}    
var lambda = Expression.Lambda<Func<StagedModel, bool>>(stage, parameter);
Stages = unitOfWork.Stages.GetStagesPagedList(1, PerPage, stage);
Aducci
  • 26,101
  • 8
  • 63
  • 67