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);
}
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 }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With