I'm trying to create a generic OrderBy(fields collection) method to use in a new EF .Net Core 2.1 and managed to write the below code:
public static Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> GetOrderByFunction(List<SortColumn> columns)
{
Type typeQueryable = typeof(IQueryable<TEntity>);
ParameterExpression argQueryable = Expression.Parameter(typeQueryable, "p");
LambdaExpression outerExpression = Expression.Lambda(argQueryable, argQueryable);
Type entityType = typeof(TEntity);
ParameterExpression arg = Expression.Parameter(entityType, "x");
Expression expr = arg;
Expression resultExp = null;
foreach (SortColumn sc in columns)
{
PropertyInfo pi = entityType.GetProperty(sc.FieldName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
Expression propertyExpr = Expression.Property(expr, pi);
Type propertyType = pi.PropertyType;
LambdaExpression lambdaExp = Expression.Lambda(propertyExpr, arg);
String methodName = string.Empty;
if (resultExp != null)
{
methodName = sc.Descending ? "ThenBy" : "ThenByDescending";
/// No generic method 'ThenBy' 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.
Expression exp = Expression.Call(typeof(Queryable), methodName, new Type[] { entityType, propertyType }, outerExpression.Body, Expression.Quote(lambdaExp));
MethodInfo minfo = typeof(Queryable).GetMethods(BindingFlags.Static | BindingFlags.Public).First(m => m.Name == methodName);
/// Method System.Linq.IOrderedQueryable`1
/// [TSource] OrderBy[TSource,TKey](System.Linq.IQueryable`1
/// [TSource],System.Linq.Expressions.Expression`1
/// [System.Func`2[TSource,TKey]]) is a generic method definition
/// Parameter name: method
resultExp = Expression.Call(minfo, exp, resultExp);
}
else
{
methodName = sc.Descending ? "OrderBy" : "OrderByDescending";
resultExp = Expression.Call(typeof(Queryable), methodName, new Type[] { entityType, propertyType }, outerExpression.Body, Expression.Quote(lambdaExp));
}
}
LambdaExpression orderedLambda = Expression.Lambda(resultExp, argQueryable);
return (Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>)orderedLambda.Compile();
}
My Sortcolumn
class just hold the field name and order direction.
public class SortColumn
{
public string FieldName { get; }
public bool Descending { get; }
public SortColumn(string fieldName, bool descending)
{
FieldName = fieldName;
Descending = descending;
}
}
It works when my collection has only a single item like Id or Description but fails to sort by more than one field.
The errors I got are commented in the snippet:
No generic method 'ThenBy' 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.
It's strange because it works fine for the OrderBy
command.
But even if I pass the OrderBy
I was unable to "join" the methods to assemble a unique expression. The error I got is:
Method System.Linq.IOrderedQueryable'1 [TSource] OrderBy[TSource,TKey](System.Linq.IQueryable'1 [TSource],System.Linq.Expressions.Expression'1 [System.Func'2[TSource,TKey]]) is a generic method definition Parameter name: method
Things I tried:
Change typeof(Queryable)
to use Enumerable
or EnumerableQuery
or even List, Use invoke, etc.
My goal is to dynamicaly pass a list of fields to order my GetAll() methods for my domain objects building a delegate function can give me something like:
var test = new List<TEntity>();
test.OrderBy(x => x.Id).ThenByDescending(x => x.Description);