Search code examples
entity-frameworklinq-to-entitiesiqueryableskip-take

How to check if IQueryable<T> has OrderBy applied before before attempting Skip() and Take()


I am trying to build an extension method that will Paginated a query. But in order to avoid exception: System.NotSupportedException was unhandled by user code Message=The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.

I'd like to check if OrderBy was applied and if not just return the query... Something like this:

public static IQueryable<T> Paginate<T>(this IQueryable<T> query, int page, int pageSize = 50, int total = -1)
{
    // check if OrderBy was applied

    // THIS DOES NOT WORK!!!
    //try
    //{
    //    var orderedQueryable = query as IOrderedQueryable<T>;
    //}
    //catch (Exception)
    //{
    //    // if the cast throws OrderBy was not applied <-- DOES NOT WORK!!!
    //    return query;
    //}
    page = (page < 1) ? 1 : page;
    var limit = (pageSize <= 0 || (total >= 0 && pageSize > total)) ? 50 : pageSize;
    var skip = (page - 1)*limit;

    return query.Skip(skip).Take(limit);
}

To make things more interesting, I am using Mycrosoft's Dynamic Expression API (aka Dynamic LINQ) so my calling code looks like

return query
         .OrderBy("Customer.LastName DESC, Customer.FirstName")
         .Paginate(1,25, 2345)
         .ToArray();

or I can invoke it using strongly typed expressions like this

return query
        .OrderByDescending(c=>c.LastName)
        .ThenBy(c=>c.FirstName)
        .Paginate(1,25,2345)
        .ToArray();

Is this type of checking possible? I tired using IOrderableQueryable<T> in method signature but Dynamic Linq does not return IOrderableQueryable when you use OrderBy(string expression) so extension would not apply...

UPDATE

Using suggestion (pointed out by @GertArnold) the only workable solution was to inspect the expression tree. However, instead of making use of entire ExpressionVisitor I simplified my solution by requiring that Paginate method must be called just after OrderBy or OrderByDescending. This allowed me to check only current node in expression tree instead of searching the whole tree. So this is what I did:

// snip....class level 
private static readonly string[] PaginationPrerequisiteMehods = new[] { "OrderBy", "OrderByDescending" };

// snip Paginate method
public static IQueryable<T> Paginate<T>(this IQueryable<T> query, int page, int pageSize = 50, int total = -1)
    {
        // require that either OrderBy or OrderByDescending was applied just before calling Paginate....
        if (query.Expression.NodeType != ExpressionType.Call)
        {
            //TODO: logging -> "You have to apply OrderBy() or OrderByDescending() just before calling Paginate()"
            return query;
        }
        var methodName = ((MethodCallExpression) query.Expression).Method.Name;
        if (!Array.Exists(PaginationPrerequisiteMehods, s => s.Equals(methodName, StringComparison.InvariantCulture)))
        {
            //TODO: logging -> "You have to apply OrderBy() or OrderByDescending() just before calling Paginate()"
            return query;
        }

        page = (page < 1) ? 1 : page;
        var limit = (pageSize <= 0 || (total >= 0 && pageSize > total)) ? 50 : pageSize;
        var skip = (page - 1)*limit;

        return query.Skip(skip).Take(limit);
    }

Solution

  • You can check the expression itself, as described here, or check the compile-time type, as described here. I think the former should work for you, because dynamic linq also adds OrderBy (or OrderByDescending) to the expression.