Junior
Junior

Reputation: 12002

How can I convert IQueryable expression object into LINQ expression?

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?

Upvotes: 1

Views: 1980

Answers (3)

Aducci
Aducci

Reputation: 26694

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);

Upvotes: 0

Ivan Stoev
Ivan Stoev

Reputation: 205849

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);

... 
...

Upvotes: 1

Servy
Servy

Reputation: 203815

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);

Upvotes: 2

Related Questions