Search code examples
c#linqdynamiclambdaexpression-trees

Creating linq expression trees for dynamic objects


I am trying to build an expression tree dynamically for sorting. The sorting will happen at the action filter of my web api. So the type of object will be unknown until runtime.

Here's the overview:
At the action filter level:

IEnumerable<object> model = null;
context.Response.TryGetContentValue(out model);
model=model.OrderByExtension(orderByField, orderDirection);
context.Response.Content=new ObjectContent<IEnumerable<object>>(model, new JsonMediaTypeFormatter());

And the extension method:

 public static IQueryable<T> OrderByExtension<T>(this IQueryable<T> source, string sortProperty, Sorting.SortingOption sortOrder)
        {
            var type = source.FirstOrDefault().GetType(); //Gets the type of object passed, since typeof(T) is only object at this point
            var property = type.GetProperty(sortProperty);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var orderByExp = Expression.Lambda(propertyAccess, parameter);
            var typeArguments = new Type[] { typeof(T), property.PropertyType };
            var methodName = sortOrder == Sorting.SortingOption.Asc ? "OrderBy" : "OrderByDescending";
            var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(orderByExp));

            return source.Provider.CreateQuery<T>(resultExp);
        }

On the Expression.Call - I get the error: No generic method 'OrderByDescending' on type 'System.Linq.Queryable' is compatible with the supplied type arguments and arguments.

I am assuming there is a mismatch between the type 'object' and the actual type when the OrderBy method is invoked.

Is there anyway to get this to work ?

Thanks in advance. ps: I also tried to create a generic method for OrderBy via .MakeGenericMethod - but no success.


Solution

  • The problem is that you're generating an expression of a function of the actual underlying type, not the type of the source expression (object). You'll need to add a conversion to your type in the generated expression.

    public static IQueryable<object> OrderByExtension(this IQueryable<object> source, string sortProperty, SortOrder sortOrder = SortOrder.Unspecified)
    {
        var sourceType = typeof(object);
        var underlyingType = source.First().GetType();
        var propertyType = underlyingType.GetProperty(sortProperty).PropertyType;
        var param = Expression.Parameter(sourceType);
        var body = Expression.Property(
            Expression.Convert(param, underlyingType), sortProperty
        );
        var lambda = Expression.Lambda(body, param);
    
        var sortMethod = sortOrder == SortOrder.Descending ? "OrderByDescending" : "OrderBy";
        var expr = Expression.Call(typeof(Queryable), sortMethod, new Type[] { sourceType, propertyType },
            source.Expression, lambda
        );
        return source.Provider.CreateQuery<object>(expr);
    }