Как передать несколько выражений в OrderBy для EF? - PullRequest
13 голосов
/ 19 ноября 2011

Я использую 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;
}

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

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

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

1 Ответ

18 голосов
/ 19 ноября 2011

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

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

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

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

...