Я не могу объяснить, почему использование 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
. Но эта теория, по-видимому, неверна . Компилятор жалуется и хочет явно иметь универсальные типы.)