Reputation: 12002
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
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
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
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