Фильтрация и сортировка списков

Всесм привет. Имеется 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 шт):

Автор решения: Alexander Petrov

Фильтрации в вашем коде нет.
Получается, что метод 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")
→ Ссылка