Метод комбинированного расширения EF orderby/thenby

Я хочу иметь возможность применять комбо-сортировку firstby / thenby следующим образом:

allOrders().sort(s => s.ProductName, s => s.OrderDate)

Поэтому, взяв за основу эту статью, я написал этот метод расширения, который отлично компилируется:

public static IQueryable<T> sort<T>(this IQueryable<T> entities, params 
  Expression<Func<T, object>>[] predicates) where T : class 
{
  var sorted = entities.OrderBy(predicates[0]);
  for (int i = 1; i < predicates.Length; i++)
    sorted = sorted.ThenBy(predicates[i]);

  return sorted;
}

И я также попробовал эту краткую версию, которая также компилируется:

public static IQueryable<T> sort<T>(this IQueryable<T> entities, params 
  Expression<Func<T, object>>[] predicates) where T : class
{
  return predicates.Skip(1).Aggregate(
    entities.OrderBy(predicates[0]),
    (aggregate, currentPredicate) => aggregate.ThenBy(currentPredicate));
}

Однако, если я попытаюсь отсортировать по DateTime, я получу это исключение:

Невозможно привести тип System.DateTime к типу System.Object. LINQ to Entities поддерживает только приведение типов примитивов или перечислений EDM.

Что я делаю неправильно? Я использую EF5.


person Bobby B    schedule 24.10.2012    source источник
comment
поэтому вы хотите отсортировать по s.ProductName, а затем по s.OrderDate   -  person COLD TOLD    schedule 25.10.2012
comment
yes в порядке, указанном в вызове метода расширения   -  person Bobby B    schedule 25.10.2012
comment
Связанный ответ stackoverflow.com/a/45486019/1300910   -  person huysentruitw    schedule 03.08.2017


Ответы (2)


Когда вы возвращаете тип значения (например, int или DateTime) из лямбда-выражения, которое возвращает object, компилятор генерирует вызов Box(), чтобы преобразовать тип значения в упакованный объект.

Анализатор выражений Entity Framework не может обработать это блочное выражение.
Единственное решение — передать строго типизированное лямбда-выражение, которое возвращает тип значения.

Для этого вы можете неправильно использовать инициализаторы коллекций:

public class OrderingCollection<TEntity> : IEnumerable {
    public void Add<TProperty>(Expression<Func<TEntity, TProperty>>) {
        ...
    }
}

public static IQueryable<T> Sort<T>(this IQueryable<T> entities, 
                                    OrderingCollection<T> o) where T : class {
    ...
}


q = q.Sort(new OrderingCollection { s => s.ProductName, s => s.OrderDate });

Инициализаторы коллекций позволяют использовать вывод типа с произвольным количеством различных параметров типа.

person SLaks    schedule 24.10.2012
comment
у этого меньше шума, чем у кортежа, но жаль, что код вызова не может быть проще. Хотя решение хорошее, спасибо. - person Bobby B; 25.10.2012

Я думаю, что ваша проблема связана с Func<T, object>. Это означает, что вы собираетесь вернуть объект независимо от того, что вы сортируете, и EF не может сопоставить его с типом столбца базы данных.

Функция OrderBy() принимает как TSource, так и TKey, поэтому вам нужно указать тип ключа для каждой сортировки, что быстро выходит из-под контроля (см. Tuple<T1,T2,T3,T4,T5,T6,T7,T8>).

person Bobson    schedule 24.10.2012
comment
Таким образом, значения кортежа будут отдельными выражениями? - person Bobby B; 25.10.2012
comment
Ну, если вы хотите напрямую работать с кортежем, то да. .Sort(Tuple.Create(s => s.ProductName, s => s.OrderDate)) передаст типизированный Tuple в вашу функцию сортировки. Но я не знаю, есть ли способ заставить это работать с произвольным количеством выражений. Я использовал его скорее в качестве примера — вам понадобятся Sort<T, TKey>(), Sort<T, TKey1, TKey2>(), Sort‹T, TKey1, TKey2, TKey3›()` и так далее. - person Bobson; 25.10.2012