Reputation: 18186
I'm trying to dynamically use reflection to do an OrderBy
on whether a given sortColumn
(string) is null or not, a ThenBy
on the sortColumn
value, and a ThenBy
on hard-coded columns. Like so:
if (!String.IsNullOrEmpty(sortColumn)) {
var descending = sortDirection == "desc";
views = views.AsQueryable()
.OrderByNull<ToDoView>(sortColumn, true) // extension method
.OrderBy<ToDoView>(sortColumn, descending, true) // extension method
.ThenBy(v => v.summary ?? v.description).ToList();
}
Using other SO answers, I was able to get the OrderBy
extension method to work:
public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source,
string orderByProperty, bool desc, bool thenBy = false) {
string command = desc ? "OrderByDescending" : "OrderBy";
if (thenBy)
command = desc ? "ThenByDescending" : "ThenBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExpression = Expression.Lambda(propertyAccess, parameter);
var resultExpression = Expression.Call(
typeof(Queryable),
command,
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}
Here is my OrderByNull
extension method:
public static IOrderedQueryable<TEntity> OrderByNull<TEntity>(this IQueryable<TEntity> source,
string orderByProperty, bool desc, bool thenBy = false) {
string command = desc ? "OrderByDescending" : "OrderBy";
if (thenBy)
command = desc ? "ThenByDescending" : "ThenBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var target = Expression.Constant(null, type);
var equalsMethod = Expression.Call(Expression.Property(parameter, orderByProperty), "Equals", null, target);
var orderByExpression = Expression.Lambda(equalsMethod, parameter);
var resultExpression = Expression.Call(
typeof(Queryable),
command,
new Type[] { type, property.PropertyType },
source.Expression,
Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}
When executing this extension method, the orderByExpression
evaluates to:
{p => p.listName.Equals(null)}
However, when trying to set the resultExpression
, it throws an exception:
System.InvalidOperationException
: 'No generic method 'OrderByDescending
' on type 'System.Linq.Queryable
' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.'
I'm not really sure how to fix this, as the expression looks right to me. Any ideas?
Here is my final OrderByNull
extension method, after the fix from the accepted answer and resolving a NullReferenceException
with the equals logic:
public static IOrderedQueryable<TEntity> OrderByNull<TEntity>(this IQueryable<TEntity> source,
string orderByProperty, bool desc, bool thenBy = false) {
string command = desc ? "OrderByDescending" : "OrderBy";
if (thenBy)
command = desc ? "ThenByDescending" : "ThenBy";
var type = typeof(TEntity);
var property = type.GetProperty(orderByProperty);
var parameter = Expression.Parameter(type, "p");
var target = Expression.Constant(null, type);
var equalsMethod = Expression.Equal(Expression.Property(parameter, orderByProperty), Expression.Constant(null, typeof(object)));
var orderByExpression = Expression.Lambda(equalsMethod, parameter);
var resultExpression = Expression.Call(
typeof(Queryable),
command,
new Type[] { type, typeof(bool) },
source.Expression,
Expression.Quote(orderByExpression));
return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}
Upvotes: 3
Views: 2635
Reputation: 205729
In the failing case you are specifying property.PropertyType
(new Type[] { type, property.PropertyType }
) as a TKey
generic type argument of the called generic method OrderBy<TSource, TKey>
, but the selector lambda result type (which should match the TKey
) is bool
, hence the exception.
To fix it, change
new Type[] { type, property.PropertyType }
to
new Type[] { type, typeof(bool) }
or more generically (can be used in both methods)
new Type[] { type, orderByExpression.ReturnType }
Upvotes: 7