Подтвердить что ты не робот

Как передать несколько выражений в OrderBy для EF?

Я использую EF 4.2, но я ожидаю, что это применимо и к EF 4 и 4.1.

Я хотел бы передать метод IQueryable<T> и multiple 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;
}

Это работает отлично, пока свойства, которые я заказываю, являются string s, но когда я пытаюсь упорядочить с помощью свойства int, я получаю исключение:

Невозможно ввести тип "System.Int32" для ввода "System.IComparable". LINQ to Entities поддерживает только листинг примитивных типов данных Entity Data Model.

Любые предложения по обходу этого или вообще для другого подхода? Я рассмотрел передачу в IEnumerable<Expression>, но тогда вам нужно было бы выяснить, как отбросить конкретный тип (например, Expression<Func<User, int>), чтобы вызвать OrderBy.

4b9b3361

Ответ 1

Я не могу объяснить, почему использование Int32 не работает, но с помощью string. Не являются ли оба элемента "примитивными" типами EDM и не реализуют IComparable? Я не понимаю различного поведения.

В любом случае, кажется, что необходимо передать в каждом выражении в коллекции конкретный тип, который он должен быть отсортирован, чтобы избежать приведения типа отказа. Другими словами, не IComparable, а вместо этого int, a string, a DateTime и т.д.

Мне удалось добиться этого в соответствии с идеей в этом ответе: Как проверить наличие OrderBy в ObjectQuery <T> дерево выражений

Определите интерфейс, который не зависит от типа сортировки, а только типа объекта. (Пример ниже обобщен для произвольных объектов. Если вы хотите, чтобы для 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 of OrderByExpression (равно User как Итак, параметр лямбда u должен быть известен как User, а затем компилятор мог получить тип от UserName как string и от UserId как int. Но эта теория, по-видимому, ошибочна Компилятор жалуется и хочет иметь общие типы явно.)