如何为EF传递多个表达式到OrderBy

本文关键字:表达式 OrderBy EF | 更新日期: 2023-09-27 18:15:05

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

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

我找到了这个答案,并在此基础上写下了下面的方法:

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

只要我排序的属性是string s,这就可以正常工作,但是当我尝试按int属性排序时,我得到一个异常:

无法强制转换类型"System"。Int32'来输入' system . iccomparable '。LINQ to Entities只支持转换实体数据模型的基本类型。

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

如何为EF传递多个表达式到OrderBy

我无法解释为什么使用Int32不工作,但使用string。不是两个EDM"原始"类型和不都实现IComparable ?我不理解这种不同的行为。

无论如何,似乎有必要将集合中的每个表达式与应该排序的具体类型一起传递,以避免失败的类型强制转换。换句话说,不是IComparable,而是int, string, DateTime等。

我已经成功地实现了这一点,沿着这个答案的思路:如何检查objectquery中order的存在表达式树

定义一个接口,使不依赖于要排序的类型,而只依赖于实体类型。(下面的例子可以推广到任意实体。如果您只希望对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实例中显式指定泛型类型参数的需要并不好,但我找不到一种方法让编译器推断类型。(我希望它会,因为编译器从ApplyOrderBy方法的query推断UserTEntity,然后我期望它知道OrderByExpressionTEntity(也等于User)。因此,lambda参数u应该被称为User,然后编译器可以从UserName派生类型为string,从UserId派生类型为int。但这个理论显然是错误的。编译器抱怨并希望显式地使用泛型类型。)