3

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);
    }
zam6ak
  • 7,229
  • 11
  • 46
  • 84
  • While not the crux of your issue, note that the expression `query as IOrderedQueryable` in the commented-out section will not throw an exception if the conversion cannot be performed. It will return `null` instead. (In a failed conversion, the `as` operator evaluates to `null` while a parenthetical cast will throw an exception.) That entire block can be replaced with `if (!(query is IOrderedQueryable)) { return query; }`. Not that this will solve your problem. – cdhowie Jun 05 '12 at 19:24
  • @cdhowie you are correct - it does not solve the issue. For some reason when 'as' conversion is performed I don't get null even when OrderBy is not applied. So null check does not help – zam6ak Jun 05 '12 at 21:28

2 Answers2

2

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.

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
1

Somehow it looks like you are trying to solve something which doesn't make sense.

You don't want to get a runtime exception when using your method. Good! But in such case don't use Dynamic Linq because that is exactly something that leads to runtime exceptions and use IOrderedQueryable for your Paginate method.

You want to use Dynamic Linq. Good! But in such case you need some tests (I mean integration tests) which will test your code at runtime and your Paginate method should be tested in the same way.

Exploring expression tree at runtime is still the same as runtime exception - it didn't inform programmer about using Paginate method on unordered data.

Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • 1
    I really don't want to use Dunamic Linq, but I have to. The grid on my web page does sorting, filtering, and grouping by making AJAX request which send string params. I can then either 1) use Dynamic Linq or 2) somehow convert these string to real expressions myself. Well Dynamic LINQ is doing #2 already. I'd like to guard Paginate method by doing logging rather than trowing exception so that other team members don't have to write test because no matter how many tests you write, you would not be able to have 100% coverage. – zam6ak Jun 05 '12 at 21:24