Фильтрация и сортировка списков
Всесм привет. Имеется Handler, возвращающий некий список объектов на фронт. (Проект Web API) У Handler есть 2 контроллера, вызывающие его, один с поиском и сортировкой, другой только с сортировкой.
Уважаемый знатоки, внимание вопрос: Как произвести поиск, если массив уже упорядочен через OrderBy и соответсвенно имеет тип IOrderedEnumerable
Изначально возращаемые типы были List<> , но пришлось изменить на IOrderedEnumerable<>.
Как можно в данной ситуации решить вопрос ?
Критериев для поиска у меня 4. (4 поля поиска на фронте т.к. это таблица)
Делать проверку на наличие запроса на поиск. И исходяи от этого плясать? Т.е. если поиск не произовдится, то просто упорядочить список по умолчанию (по дате) и вернуть на фронт. Если же с запросом пришла строка поиска, то сделать switch и в ветке свитча сначала произвести поиск по списку, пока он все еще List, а потом в этой ветке произвести сортировку, если требуется.
ИЛи может есть какие-то мето поиска по IOrderedEnumerable ?
на данный момент код таков :
readonly IReadonlyRepository<BlackListEntity> _readonlyRepository;
public GetBlackListQueryHandler(
IReadonlyRepository<BlackListEntity> readonlyRepository)
{
_readonlyRepository = readonlyRepository;
}
public async Task<IOrderedEnumerable<BlackList>> Handle(GetBlackListRequest request, CancellationToken cancellationToken)
{
IOrderedEnumerable<BlackList> x = null;
var result = await _readonlyRepository.GetAsync(cancellationToken);
var dataForSort = result.Value.Select(x => new BlackList
{
ItemId = x.Id,
...
}).ToList();
switch (request.SortBy)
{
case "LastName":
x = dataForSort.OrderBy(x => x.LastName);
break;
case "LastNameDecs":
x = dataForSort.OrderByDescending(x => x.LastName);
break;
case "FirstName":
x = dataForSort.OrderBy(x => x.FirstName);
break;
case "FirstNameDesc":
x = dataForSort.OrderByDescending(x => x.FirstName);
break;
case "MiddleName":
x = dataForSort.OrderBy(x => x.MiddleName);
break;
case "MiddleNameDesc":
x = dataForSort.OrderByDescending(x => x.MiddleName);
break;
case "PhoneNumber":
x = dataForSort.OrderBy(x => x.PhoneNumber);
break;
case "PhoneNumberDesc":
x = dataForSort.OrderByDescending(x => x.PhoneNumber);
break;
case "CreateDate":
x = dataForSort.OrderBy(x => x.CreateDate);
break;
case "CreateDateDesc":
x = dataForSort.OrderByDescending(x => x.CreateDate);
break;
}
return x;
Может еще можно как-то фильтрации упростить ? а то конструкция с свичтем и сценарием на каждую фильтрацию кажется несколько кастыльной
Ответы (1 шт):
Фильтрации в вашем коде нет.
Получается, что метод GetAsync возвращает все данные из какой-то таблицы.
Ладно, допустим, что фильтрация опущена для краткости.
Если что, Select - это проекция.
Метод .ToList(); материализует запрос. Все данные будут получены из БД и сохранены в списке. Дальнейший код сортирует уже этот список. Это неправильно! Фильтрацию и сортировку нужно выполнять в БД! Это практически всегда будет быстрей и эффективней.
Вашу задачу можно решить с помощью разных библиотек для EF. Среди них Dynamic LINQ - она позволяет задавать параметры выражений в виде строки, LinqKit - она позволяет динамически составлять запросы с помощью PredicateBuilder.
Однако, можно обойтись без дополнительных библиотек. Для вашего случая код может быть простейшим.
public async Task<IEnumerable<BlackList>> Handle<T>(
Expression<Func<BlackList, T>> expression, Sort sort)
{
if (sort == Sort.Asc)
return await _readonlyRepository.GetAsync()
.OrderBy(expression)
.ToListAsync();
else
return await _readonlyRepository.GetAsync()
.OrderByDescending(expression)
.ToListAsync();
}
Тут подразумевается, что метод GetAsync возвращает IQueryable<BlackList>.
CancellationToken опущен для краткости.
В качестве параметра передаём Expression.
В коде это выглядит так:
await Handle(blackList => blackList.LastName, Sort.Desc);
То есть вместо строки "LastNameDesc" передаём два параметра: expression со свойством, по которому сортировать, и перечисление с направлением сортировки.
Возвращаем из метода IEnumerable. Можно сделать List. Тому, кто будет потреблять результат этого метода, не важен IOrderedEnumerable.
Перечисление Sort выглядит следующим образом:
public enum Sort { Asc, Desc }
Если угодно, можно вместо него использовать параметр bool isAscending.
Я вспомнил о существовании метода EF.Property, который принимает имя свойства как раз в виде строки. С его помощью код может быть реализован следующим образом:
public async Task<List<BlackList>> Handle(string sortProperty, Sort sort)
{
if (sort == Sort.Asc)
return await _readonlyRepository.GetAsync()
.OrderBy(x => EF.Property<object>(x, sortProperty))
.ToListAsync();
else
return await _readonlyRepository.GetAsync()
.OrderByDescending(x => EF.Property<object>(x, sortProperty))
.ToListAsync();
}
Вызывать так:
await Handle("LastName", Sort.Desc)
При желании можно избавиться от второго параметра и передавать направление сортировки вместе с именем, как в вашем первоначальном варианте:
public async Task<List<BlackList>> Handle(string sortProperty)
{
if (sortProperty.EndsWith("Desc"))
{
sortProperty = sortProperty[..^4];
return await _readonlyRepository.GetAsync()
.OrderByDescending(x => EF.Property<object>(x, sortProperty))
.ToListAsync();
}
else
return await _readonlyRepository.GetAsync()
.OrderBy(x => EF.Property<object>(x, sortProperty))
.ToListAsync();
}
Вызов:
await Handle("LastNameDesc")