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...
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);
}
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.