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


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

我想传递一个 IQueryable<T> 和多个 Expression<Func<TSource, TKey>> 方法并适用该方法 OrderBy 和 ThenBy 到了 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;
}

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

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

有任何建议可以解决这个问题,或者完全采用不同的方法吗?我考虑过去了 IEnumerable<Expression>,但随后需要弄清楚如何回归到特定类型(例如 Expression<Func<User, int>)打电话 OrderBy


4190
2017-11-18 23:38


起源



答案:


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

无论如何,似乎有必要传递集合中的每个表达式,并使用具体类型对其进行排序以避免失败的类型转换。换句话说,不是 IComparable,而是一个 int, 一个 string, 一个 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);
}

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

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 实例并不好,但我找不到方法,以便编译器推断出类型。 (我希望它会,因为编译器会推断出 User 如 TEntity 从 query 为了 ApplyOrderBy 方法,然后我预计它知道了 TEntity 的 OrderByExpression (等于 User 以及)。所以lambda参数 u 应该被称为a User 然后编译器可以从中派生类型 UserName 如 string 从 UserId 如 int。但这个理论显然是错误的。编译器抱怨并希望明确地拥有泛型类型。)


16
2017-11-19 14:56



+1,很棒的答案,它完美无缺,正是我所希望的:) - Jeff Ogata
我一直在努力解决这个问题,并且可能会放弃,但是我应该可以使用Expression <Func <TEntity,object >>,然后当你调用ApplyOrderBy检查Lambda表达式时,找出参数的类型,然后构建一个正确键入的新Lambda表达式。虽然看起来太辛苦了。 - Dale Burrell
优秀的答案,为我开箱即用'开箱即用' - Hugh Jones


答案:


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

无论如何,似乎有必要传递集合中的每个表达式,并使用具体类型对其进行排序以避免失败的类型转换。换句话说,不是 IComparable,而是一个 int, 一个 string, 一个 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);
}

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

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 实例并不好,但我找不到方法,以便编译器推断出类型。 (我希望它会,因为编译器会推断出 User 如 TEntity 从 query 为了 ApplyOrderBy 方法,然后我预计它知道了 TEntity 的 OrderByExpression (等于 User 以及)。所以lambda参数 u 应该被称为a User 然后编译器可以从中派生类型 UserName 如 string 从 UserId 如 int。但这个理论显然是错误的。编译器抱怨并希望明确地拥有泛型类型。)


16
2017-11-19 14:56



+1,很棒的答案,它完美无缺,正是我所希望的:) - Jeff Ogata
我一直在努力解决这个问题,并且可能会放弃,但是我应该可以使用Expression <Func <TEntity,object >>,然后当你调用ApplyOrderBy检查Lambda表达式时,找出参数的类型,然后构建一个正确键入的新Lambda表达式。虽然看起来太辛苦了。 - Dale Burrell
优秀的答案,为我开箱即用'开箱即用' - Hugh Jones