Vidmantas Blazevicius
Vidmantas Blazevicius

Reputation: 4802

Dynamic OrderBy expression with bool predicate

I have a model class

public class MyModel
{
    public long Id { get; set; }

    public string Name { get; set; }
}

I am currently returning an ordered list (by name) of MyModel from database :

return await Db.MyModel
    .AsNoTracking()
    .OrderBy(x => x.Name)

However, one of the values in database is "N/A" and I am looking to order by some selector whilst leaving "N/A" last, so basically:

 return await Db.MyModel
    .AsNoTracking()
    .OrderBy(x => x.Name == "N/A)
    .ThenBy(x => x.Name)

I created an IQueryable extension, but I am getting an error on the Expression.Call(...)

InvalidOperationException: No generic method 'OrderBy' 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.

public static IQueryable<TSource> OrderNALast<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, string>> selector)
{
    var expression = source.Expression;
    var parameter = Expression.Parameter(typeof(TSource), "x");

    var left = (MemberExpression)selector.Body;
    var right = Expression.Constant("N/A");
    var predicateBody = Expression.Equal(left, right);

    var lambda = Expression.Lambda<Func<TSource, bool>>(predicateBody, parameter);

    expression = Expression.Call(
        typeof(Queryable), 
        "OrderBy",
        new[] { source.ElementType, left.Type },
        expression,
        lambda);

    var appendedQuery = (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(expression);
    return appendedQuery.ThenBy(selector);
}

Upvotes: 1

Views: 283

Answers (1)

iSpain17
iSpain17

Reputation: 3053

Oh, I think I see the problem.

In Expression.Call, the third argument requires the static method's generic type arguments to call.

Queryable.OrderBy() has two overloads:

public static System.Linq.IOrderedQueryable<TSource> OrderBy<TSource,TKey> (this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<Func<TSource,TKey>> keySelector, System.Collections.Generic.IComparer<TKey> comparer);

and

public static System.Linq.IOrderedQueryable<TSource> OrderBy<TSource,TKey> (this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<Func<TSource,TKey>> keySelector);

But instead of giving

TSource and bool

as arguments, you are giving it

TSource and string

as arguments.

So it should be:

expression = Expression.Call(
        typeof(Queryable), 
        "OrderBy",
        new[] { typeof(TSource), typeof(bool) },
        expression,
        lambda);

(Or you can also use source.ElementType for the first type.)

Upvotes: 1

Related Questions