Setting a dynamic sort name field in a linq query

2019-05-27 12:48发布

问题:

This question already has an answer here:

  • Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T> 19 answers
  • How to use a string to create a EF order by expression? 1 answer

I want to be able to get an OrderBy query working with a lambda expression so that I get a SQL query with the TOP(n) key word (big performance boost).

I am able to do this if I specifiy ...

PaginatedList = query.OrderBy(x => x.QuoteID).Skip(() => skipValue).Take(() => pageSize)

But because I want the orderBy field to be dynamic through a UI selection of a name I want to do something like this:

var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
Expression<Func<Data.Quote, object>> orderField = x => propertyInfo.GetValue(x, null);
PaginatedList = query.OrderBy(orderField).Skip(() => skipValue).Take(() => pageSize)

This gives me the error:

"LINQ to Entities does not recognize the method 'System.Object GetValue(System.Object)' method, and this method cannot be translated into a store expression."

I tried this that's not of type Expression<Func<T, object>>

var propertyInfo = typeof(Data.Quote).GetProperty(sortName);
Func<Data.Quote, object> orderField = x => propertyInfo.GetValue(x, null);
PaginatedList = query.OrderBy(x => orderField).Skip(() => skipValue).Take(() => pageSize)

And I get this error:

"Unable to create a constant value of type [...]. Only primitive types or enumeration types are supported in this context"

I'm sure there is a way to achieve this but at the moment not sure how.

回答1:

Here is how to achieve what you want:

var propertyInfo = typeof(Data.Quote).GetProperty(sortName);

ParameterExpression parameter = Expression.Parameter(typeof(T), "s");
MemberExpression property = Expression.Property(parameter, propertyInfo);
LambdaExpression sort = Expression.Lambda(property, parameter);

MethodCallExpression call = Expression.Call(
                                         typeof(Queryable),
                                         "OrderBy",
                                         new[] {typeof(T), property.Type},
                                         Query.Expression,
                                         Expression.Quote(sort));

var orderedQuery = (IOrderedQueryable<T>)Query.Provider.CreateQuery<T>(call);

PaginatedList = orderedQuery.Skip(skipValue).Take(pageSize);


回答2:

Instead of that you need to create an expression to select that property.From this source:

public static class Utility
{
    //makes expression for specific prop
    public static Expression<Func<TSource, object>> GetExpression<TSource>(string propertyName)
    {
        var param = Expression.Parameter(typeof(TSource), "x");
        Expression conversion = Expression.Convert(Expression.Property
        (param, propertyName), typeof(object));   //important to use the Expression.Convert
        return Expression.Lambda<Func<TSource, object>>(conversion, param);
    }


    public static IOrderedQueryable<TSource> 
    OrderBy<TSource>(this IQueryable<TSource> source, string propertyName)
    {
        return source.OrderBy(GetExpression<TSource>(propertyName));
    }
}

Then you can order by as I show below:

var result=Query.OrderBy(sortName)...;


回答3:

The value being copied in to propertyInfo is just an Object, as that is the type returned by GetProperty(). Hover over var and you will confirm this.

The GetValue method doesn't exist for an Object, so you need to cast it to the right type before making the call to GetValue.