如何在尝试Skip()和Take()之前检查IQueryable <t>是否已应用OrderBy </t>

时间:2012-06-05 19:20:20

标签: entity-framework linq-to-entities iqueryable skip-take

我正在尝试构建一个将Paginated查询的扩展方法。 但为了避免异常: 用户代码未处理System.NotSupportedException   Message =方法'Skip'仅支持LINQ to Entities中的排序输入。必须在方法'Skip'之前调用'OrderBy'方法。

我想检查是否已应用OrderBy,如果不是,则只返回查询... 像这样:

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

为了让事情变得更有趣,我正在使用Mycrosoft的Dynamic Expression API(又名动态LINQ),所以我的调用代码看起来像

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

或者我可以使用像这样的强类型表达式来调用它

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

这种检查是否可行? 我厌倦了在方法签名中使用IOrderableQueryable<T>但是当您使用OrderBy(字符串表达式)时,Dynamic Linq不会返回IOrderableQueryable,因此扩展将不适用...

更新

使用建议(由@GertArnold指出)唯一可行的解​​决方案是检查表达式树。但是,不要使用整个ExpressionVisitor,而是通过要求必须在OrderByOrderByDescending之后调用Paginate方法来简化我的解决方案。这使我只能检查表达式树中的当前节点,而不是搜索整个树。 所以这就是我所做的:

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

2 个答案:

答案 0 :(得分:2)

您可以按照here所述检查表达式本身,或检查编译时类型,如here所述。我认为前者应该适合你,因为动态linq还会在表达式中添加OrderBy(或OrderByDescending)。

答案 1 :(得分:1)

不知何故,看起来你正试图解决一些没有意义的事情。

使用方法时,您不希望获得运行时异常。好!但在这种情况下,请不要使用Dynamic Linq,因为这正是导致运行时异常并为IOrderedQueryable方法使用Paginate的原因。

您想使用Dynamic Linq。好!但在这种情况下,您需要一些测试(我的意思是集成测试),它将在运行时测试您的代码,并且您的Paginate方法应该以相同的方式进行测试。

在运行时探索表达式树仍然与运行时异常相同 - 它没有通知程序员在无序数据上使用Paginate方法。