0

I'm trying to write a dynamic query but having trouble when dealing with dynamic types in the OrderBy section of my query. I've seen some solutions using method call expressions, which may work for me, but I don't know how to fit that into my existing code where I am using method chaining on either side. It's possible I can just use dynamic as my type but I don't think that will work with Nullable types.

public async Task<IEnumerable<MyEntityModel>> GetEntities(QueryEntityResource request)
{
    IQueryable<MyEntityModel> queryableData = ...;
    Expression whereLambdaExp = ...;

    queryableData = queryableData.Where(whereLambdaExp);

    ParameterExpression param = Expression.Parameter(typeof(MyEntityModel));
    PropertyInfo property = typeof(MyEntityModel).GetProperty(request.sortModel.ColId);
    Expression propExpr = Expression.Property(param, property);
    var lambdaExpression = Expression.Lambda<Func<MyEntityModel, dynamic>>(propExpr , new ParameterExpression[] { param }); // can't use dynamic

    // Errors here; can't use dynamic type in lambda expression -- Need to specify types
    if (request.sortModel.Sort == "asc")
    {
        queryableData = queryableData.OrderBy(lambdaExpression);
    }
    if (request.sortModel.Sort == "desc")
    {
        queryableData = queryableData.OrderByDescending(lambdaExpression);
    }

    queryableData = queryableData.Skip(request.StartRow).Take(request.EndRow - request.StartRow);

    return queryableData.ToListAsync();
}

If I use dynamic it won't work with my nullable types:

System.ArgumentException: Expression of type 'System.Nullable`1[System.Int32]' cannot be used for return type 'System.Object'

xb4rrm27m5
  • 123
  • 1
  • 7
  • FYI, Expression.Lambda has a `params` variant that will create the array for you so you don't need `new ParameterExpression[]` (and, you wouldn't need `ParameterExpression` since `new[]` can infer the array type from the expressions in the `{}`). – NetMage Jun 25 '20 at 20:10
  • Yes, the problem with building `Expression`s to use with LINQ query operators is it propagates upward (like a virus). Since `OrderBy` is generic, you need to do the right method call for `OrderBy` with the generic properties fixed, so you have to build that as an `Expression` tree as well and since `queryableData` is an `IQueryable` you will need to replace `Expression` instead. – NetMage Jun 25 '20 at 20:17
  • @NetMage Would you please give me an example with code and how it would fit in with my existing code? – xb4rrm27m5 Jun 25 '20 at 20:19

1 Answers1

0

This is untested; but I think it is close to what you need (I feel like I've written this code before... also, see this answer):

You can build calls to OrderBy instantiated to the types you need and then build a new IQueryable. My method removes the async as I couldn't build a quick test for that in LINQPad.

public IEnumerable<MyEntityModel> GetEntities(QueryEntityResource request) {
    IQueryable<MyEntityModel> queryableData = default;
    Expression<Func<MyEntityModel, bool>> whereLambdaExp = default;

    queryableData = queryableData.Where(whereLambdaExp);

    var param = Expression.Parameter(typeof(MyEntityModel));
    var propExpr = Expression.Property(param, request.sortModel.ColId);
    var lambdaExpression = Expression.Lambda(propExpr, param);

    if (request.sortModel.Sort == "asc" || request.sortModel.Sort == "desc")
        queryableData = queryableData.Provider.CreateQuery<MyEntityModel>(Expression.Call(typeof(Queryable), request.sortModel.Sort == "asc" ? "OrderBy" : "OrderByDescending",
                        new[] { typeof(MyEntityModel), propExpr.Type }, queryableData.Expression, Expression.Quote(lambdaExpression)));

    queryableData = queryableData.Skip(request.StartRow).Take(request.EndRow - request.StartRow);

    return queryableData.ToList();
}
NetMage
  • 26,163
  • 3
  • 34
  • 55
  • @xb4rrm27m5 Note I didn't need the extension method any more after I switched to a better `Expression.Call` overload. – NetMage Jun 25 '20 at 23:09