【问题标题】:How to pass multiple Expressions to OrderBy for EF?如何将多个表达式传递给 EF 的 OrderBy?
【发布时间】:2011-11-18 23:38:34
【问题描述】:

我使用的是 EF 4.2,但我希望这也适用于 EF 4 和 4.1。

我想将一个IQueryable<T> 和多个Expression<Func<TSource, TKey>> 传递给一个方法,并让该方法酌情将OrderByThenBy 应用于IQueryable<T>

我找到了this answer,并以此为基础编写了如下方法:

public IQueryable<User> ApplyOrderBy(IQueryable<User> query, IEnumerable<Expression<Func<User, IComparable>>> orderBy)
{
    if (orderBy == null) 
    {
        return query;
    }

    IOrderedQueryable<User> output = null;

    foreach(var expression in orderBy)
    {
        if (output == null)
        {
            output = query.OrderBy(expression);
        }
        else
        {
            output = output.ThenBy(expression);
        }
    }

    return output ?? query;
}

只要我订购的属性是 strings 就可以正常工作,但是当我尝试按 int 属性订购时,我得到一个异常:

无法将类型“System.Int32”转换为类型“System.IComparable”。 LINQ to Entities 仅支持转换实体数据模型基元类型。

有什么建议可以解决这个问题,或者完全不同的方法?我考虑过传入IEnumerable&lt;Expression&gt;,但随后需要弄清楚如何转换回特定类型(例如Expression&lt;Func&lt;User, int&gt;)以调用OrderBy

【问题讨论】:

    标签: c# linq entity-framework-4 linq-to-entities entity-framework-4.1


    【解决方案1】:

    我无法解释为什么使用Int32 不起作用但使用string。两种 EDM 都不是“原始”类型,也不是都实现IComparable?我不明白不同的行为。

    无论如何,似乎有必要将集合中的每个表达式都传递给它应该作为排序依据的具体类型,以避免类型转换失败。换句话说,不是IComparable,而是intstringDateTime 等。

    我按照这个答案中的想法成功地实现了这一点:How to check for the presence of an OrderBy in a ObjectQuery<T> expression tree

    定义一个接口,它依赖于排序依据的类型,但仅依赖于实体类型。 (下面的示例推广到任意实体。如果您只希望 User 删除通用参数并将可查询对象中的 TEntity 替换为 User。)

    public interface IOrderByExpression<TEntity> where TEntity : class
    {
        IOrderedQueryable<TEntity> ApplyOrderBy(IQueryable<TEntity> query);
        IOrderedQueryable<TEntity> ApplyThenBy(IOrderedQueryable<TEntity> query);
    }
    

    定义该接口的实现,该接口现在将要排序的类型作为第二个泛型参数:

    public class OrderByExpression<TEntity, TOrderBy> : IOrderByExpression<TEntity>
        where TEntity : class
    {
        private Expression<Func<TEntity, TOrderBy>> _expression;
        private bool _descending;
    
        public OrderByExpression(Expression<Func<TEntity, TOrderBy>> expression,
            bool descending = false)
        {
            _expression = expression;
            _descending = descending;
        }
    
        public IOrderedQueryable<TEntity> ApplyOrderBy(
            IQueryable<TEntity> query)
        {
            if (_descending)
                return query.OrderByDescending(_expression);
            else
                return query.OrderBy(_expression);
        }
    
        public IOrderedQueryable<TEntity> ApplyThenBy(
            IOrderedQueryable<TEntity> query)
        {
            if (_descending)
                return query.ThenByDescending(_expression);
            else
                return query.ThenBy(_expression);
        }
    }
    

    那么ApplyOrderBy 将如下所示:

    public IQueryable<TEntity> ApplyOrderBy<TEntity>(IQueryable<TEntity> query,
        params IOrderByExpression<TEntity>[] orderByExpressions)
        where TEntity : class
    {
        if (orderByExpressions == null)
            return query;
    
        IOrderedQueryable<TEntity> output = null;
    
        foreach (var orderByExpression in orderByExpressions)
        {
            if (output == null)
                output = orderByExpression.ApplyOrderBy(query);
            else
                output = orderByExpression.ApplyThenBy(output);
        }
    
        return output ?? query;
    }
    

    并且可以通过以下方式使用:

    var query = context.Users ... ;
    
    var queryWithOrderBy = ApplyOrderBy(query,
        new OrderByExpression<User, string>(u => u.UserName),    // a string, asc
        new OrderByExpression<User, int>(u => u.UserId, true));  // an int, desc
    
    var result = queryWithOrderBy.ToList(); // didn't throw an exception for me
    

    OrderByExpression 实例中明确指定泛型类型参数的需要并不好,但我找不到让编译器推断类型的方法。 (我希望它会,因为编译器从query 推断TEntityApplyOrderBy 方法,然后我希望它知道OrderByExpressionTEntity (等于User 为好吧). 所以 lambda 参数u 应该被称为User 然后编译器可以从UserName 派生类型为string 和从UserId 作为int。但是这个理论显然是错误的. 编译器抱怨并希望明确地拥有泛型。)

    【讨论】:

    • +1,很好的答案,它完美运行,正是我所希望的 :)
    • 我一直在为此苦苦挣扎,可能会放弃,但我认为应该可以使用 Expression>,然后在调用 ApplyOrderBy 时检查 Lambda 表达式,找出参数的类型,然后构建一个正确键入的新 Lambda 表达式。不过好像太辛苦了。
    • 不仅仅是很棒的答案,我希望我能投票 1000 次。
    猜你喜欢
    • 2011-06-30
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    • 2020-02-25
    • 1970-01-01
    • 2020-01-14
    • 2019-12-13
    • 2010-11-14
    相关资源
    最近更新 更多