Список выражений <Func <T, TProperty >>
Я ищу способ хранения коллекции Expression<Func<T, TProperty>>
, используемой для упорядочивания элементов, а затем для выполнения сохраненного списка с объектом IQueryable<T>
(основным провайдером является Entity Framework).
Например, я хотел бы сделать что-то вроде этого (это псевдо-код):
public class Program
{
public static void Main(string[] args)
{
OrderClause<User> orderBys = new OrderClause<User>();
orderBys.AddOrderBy(u => u.Firstname);
orderBys.AddOrderBy(u => u.Lastname);
orderBys.AddOrderBy(u => u.Age);
Repository<User> userRepository = new Repository<User>();
IEnumerable<User> result = userRepository.Query(orderBys.OrderByClauses);
}
}
Предложение order by (свойство, которое нужно заказать):
public class OrderClause<T>
{
public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
{
_list.Add(orderBySelector);
}
public IEnumerable<Expression<Func<T, ???>>> OrderByClauses
{
get { return _list; }
}
}
Репозиторий с моим методом запроса:
public class Repository<T>
{
public IEnumerable<T> Query(IEnumerable<OrderClause<T>> clauses)
{
foreach (OrderClause<T, ???> clause in clauses)
{
_query = _query.OrderBy(clause);
}
return _query.ToList();
}
}
Моя первая идея состояла в том, чтобы преобразовать Expression<Func<T, TProperty>>
в строку (имя свойства для сортировки). Таким образом, в основном вместо хранения типизированного списка (что невозможно, потому что TProperty не является константой), я сохраняю список строк со свойствами для сортировки.
Но это не работает, потому что тогда я не могу восстановить Expression
назад (мне это нужно, потому что IQueryable.OrderBy принимает параметр Expression<Func<T, TKey>>
as).
Я также попытался динамически создать Expression (с помощью Expression.Convert), чтобы иметь Expression<Func<T, object>>
, но затем я получил исключение из фреймворка сущности, который сказал, что он не смог обработать выражение Expression.Convert.
Если возможно, я не хочу использовать внешнюю библиотеку, такую как Динамическая библиотека Linq.
Ответы
Ответ 1
Это один из немногих случаев, когда может быть подходящим решение dynamic
/reflection.
Я думаю, тебе нужно что-то вроде этого? (Я читал между строками и делал некоторые изменения в вашей структуре, где я считал нужным).
public class OrderClauseList<T>
{
private readonly List<LambdaExpression> _list = new List<LambdaExpression>();
public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
{
_list.Add(orderBySelector);
}
public IEnumerable<LambdaExpression> OrderByClauses
{
get { return _list; }
}
}
public class Repository<T>
{
private IQueryable<T> _source = ... // Don't know how this works
public IEnumerable<T> Query(OrderClause<T> clauseList)
{
// Needs validation, e.g. null-reference or empty clause-list.
var clauses = clauseList.OrderByClauses;
IOrderedQueryable<T> result = Queryable.OrderBy(_source,
(dynamic)clauses.First());
foreach (var clause in clauses.Skip(1))
{
result = Queryable.ThenBy(result, (dynamic)clause);
}
return result.ToList();
}
}
Ключевым трюком становится С# dynamic
, чтобы сделать ужасное разрешение перегрузки и тип-вывода для нас. Что еще, я считаю, что выше, несмотря на использование dynamic
, на самом деле безопасен по типу!
Ответ 2
Один из способов сделать это - "сохранить" все классовые предложения в виде Func<IQueryable<T>, IOrderedQueryable<T>>
(то есть функции, вызывающей методы сортировки):
public class OrderClause<T>
{
private Func<IQueryable<T>, IOrderedQueryable<T>> m_orderingFunction;
public void AddOrderBy<TProperty>(Expression<Func<T, TProperty>> orderBySelector)
{
if (m_orderingFunction == null)
{
m_orderingFunction = q => q.OrderBy(orderBySelector);
}
else
{
// required so that m_orderingFunction doesn't reference itself
var orderingFunction = m_orderingFunction;
m_orderingFunction = q => orderingFunction(q).ThenBy(orderBySelector);
}
}
public IQueryable<T> Order(IQueryable<T> source)
{
if (m_orderingFunction == null)
return source;
return m_orderingFunction(source);
}
}
Таким образом, вам не нужно иметь дело с отражением или dynamic
, весь этот код безопасен по типу и относительно легко понять.
Ответ 3
Вы можете хранить ваши лямбда-выражения в коллекции как экземпляры типа LambdaExpression
.
Или даже лучше, сохраните определения сортировки, каждый из которых, помимо выражения, также сохраняет направление сортировки.
Предположим, что у вас есть следующий метод расширения
public static IQueryable<T> OrderBy<T>(
this IQueryable<T> source,
SortDefinition sortDefinition) where T : class
{
MethodInfo method;
Type sortKeyType = sortDefinition.Expression.ReturnType;
if (sortDefinition.Direction == SortDirection.Ascending)
{
method = MethodHelper.OrderBy.MakeGenericMethod(
typeof(T),
sortKeyType);
}
else
{
method = MethodHelper.OrderByDescending.MakeGenericMethod(
typeof(T),
sortKeyType);
}
var result = (IQueryable<T>)method.Invoke(
null,
new object[] { source, sortDefinition.Expression });
return result;
}
и аналогичный метод для ThenBy
. Тогда вы можете сделать что-то вроде
myQueryable = myQueryable.OrderBy(sortDefinitions.First());
myQueryable = sortDefinitions.Skip(1).Aggregate(
myQueryable,
(current, sortDefinition) => current.ThenBy(sortDefinition));
Вот определения SortDefinition
и MethodHelper
public class SortDefinition
{
public SortDirection Direction
{
get;
set;
}
public LambdaExpression Expression
{
get;
set;
}
}
internal static class MethodHelper
{
static MethodHelper()
{
OrderBy = GetOrderByMethod();
ThenBy = GetThenByMethod();
OrderByDescending = GetOrderByDescendingMethod();
ThenByDescending = GetThenByDescendingMethod();
}
public static MethodInfo OrderBy
{
get;
private set;
}
public static MethodInfo ThenBy
{
get;
private set;
}
public static MethodInfo OrderByDescending
{
get;
private set;
}
public static MethodInfo ThenByDescending
{
get;
private set;
}
private static MethodInfo GetOrderByMethod()
{
Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.OrderBy((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
}
private static MethodInfo GetThenByMethod()
{
Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.ThenBy((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
}
private static MethodInfo GetOrderByDescendingMethod()
{
Expression<Func<IQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.OrderByDescending((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
}
private static MethodInfo GetThenByDescendingMethod()
{
Expression<Func<IOrderedQueryable<object>, IOrderedQueryable<object>>> expr =
q => q.ThenByDescending((Expression<Func<object, object>>)null);
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
}
}
Ответ 4
в EF Core вы можете использовать следующий вспомогательный класс
public static class OrderBuilder
{
public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> queryable, params Tuple<Expression<Func<TSource, object>>, bool>[] keySelectors)
{
if (keySelectors == null || keySelectors.Length == 0) return queryable;
return keySelectors.Aggregate(queryable, (current, keySelector) => keySelector.Item2 ? current.OrderDescending(keySelector.Item1) : current.Order(keySelector.Item1));
}
private static bool IsOrdered<TSource>(this IQueryable<TSource> queryable)
{
if (queryable == null) throw new ArgumentNullException(nameof(queryable));
return queryable.Expression.Type == typeof(IOrderedQueryable<TSource>);
}
private static IQueryable<TSource> Order<TSource, TKey>(this IQueryable<TSource> queryable, Expression<Func<TSource, TKey>> keySelector)
{
if (!queryable.IsOrdered()) return queryable.OrderBy(keySelector);
var orderedQuery = queryable as IOrderedQueryable<TSource>;
return (orderedQuery ?? throw new InvalidOperationException()).ThenBy(keySelector);
}
private static IQueryable<TSource> OrderDescending<TSource, TKey>(this IQueryable<TSource> queryable, Expression<Func<TSource, TKey>> keySelector)
{
if (!queryable.IsOrdered()) return queryable.OrderByDescending(keySelector);
var orderedQuery = queryable as IOrderedQueryable<TSource>;
return (orderedQuery ?? throw new InvalidOperationException()).ThenByDescending(keySelector);
}
}
и затем используйте его как... этот пример ниже с классом с именем Player со следующими членами.. (код сокращен для краткости)
public class Player
{
...
public string FirstName { get; set;
public int GenderTypeId { get; set; }
public int PlayingExperience { get; set; }
Вы можете комбинировать упорядочения по своему усмотрению, сортировку по полу, игровой опыт по убыванию (обратите внимание на истинное значение кортежа) и имя.
var combinedOrder = new[]
{
new Tuple<Expression<Func<Player, object>>, bool>(p => p.GenderTypeId, false),
new Tuple<Expression<Func<Player, object>>, bool>(p => p.PlayingExperience, true),
new Tuple<Expression<Func<Player, object>>, bool>(p => p.FirstName, false),
};
и просто сделать заказ следующим образом
var data = context.Set<Player>()
.OrderBy(combinedOrder)
.ToArray();