如何将多个表达式传递给OrderBy for EF?

Jef*_*ata 13 c# linq linq-to-entities entity-framework-4 entity-framework-4.1

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

我想通过一个IQueryable<T>和多个Expression<Func<TSource, TKey>>的方法和具有应用该方法OrderBy,并ThenByIQueryable<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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

Sla*_*uma 18

我无法解释为什么使用一个Int32不起作用但使用一个string.是不是EDM都是"原始"类型而不是两者都实现IComparable?我不明白不同的行为.

无论如何,似乎有必要传递集合中的每个表达式,并使用它应该排序的具体类型,以避免失败的类型转换.换句话说,不是IComparable,而是a int,a string,a DateTime等.

在这个答案中,我成功地实现了这个想法:如何检查ObjectQuery <T>表达式树中是否存在OrderBy

定义一个依赖于要排序的类型但仅依赖于实体类型的接口.(下面的示例推广到任意实体.如果您只想User删除泛型参数并TEntity在查询中替换User.)

public interface IOrderByExpression<TEntity> where TEntity : class
{
    IOrderedQueryable<TEntity> ApplyOrderBy(IQueryable<TEntity> query);
    IOrderedQueryable<TEntity> ApplyThenBy(IOrderedQueryable<TEntity> query);
}
Run Code Online (Sandbox Code Playgroud)

定义该接口的实现,现在将该类型作为第二个通用参数进行排序:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后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;
}
Run Code Online (Sandbox Code Playgroud)

它可以使用以下方式:

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
Run Code Online (Sandbox Code Playgroud)

需要在OrderByExpression实例中明确指定泛型类型参数并不好,但我找不到一种方法,以便编译器推断出类型.(我希望它会,因为编译器推断UserTEntityqueryApplyOrderBy方法,那么我预计其知道TEntityOrderByExpression(等于User为好).因此,拉姆达参数u应该被称为一个User,然后编译器可以从派生型 UserName作为stringUserId作为int.但这种理论显然是错误的.编译器会抱怨,并希望有泛型类型明确的.)