C# LinQ: фильтровать объект[] с элементами, содержащими строку (или ее часть) из строки[]

У меня есть запрос «Лицензии», который возвращает объект Licenses[] следующим образом:

var licenses = await _licenseRepository.SearchAsync(ct); //=> CT = CancellationToken

и фильтр запросов, который позволяет фильтровать по идентификатору или имени:

public sealed record Filter(LicenseTypeValues[] Id, string[] Name);

Таким образом, я должен иметь возможность передать строку [] в качестве фильтра «Имя» (например), и я должен вернуть каждую лицензию, имя которой содержит любое из имен в строке [] - или часть их -.

Давайте рассмотрим следующий результат запроса лицензий без фильтра:

{
    "count": 5,
    "results": [
        {
            "id": 15005,
            "name": "Windows 2012",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15027,
            "name": "Windows 2016",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15030,
            "name": "Windows 2022",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-08-11T10:29:36Z"
        },
        {
            "id": 15032,
            "name": "Ubuntu 23",
            "maxCpu": null,
            "parentId": 15,
            "created": "2023-05-22T14:56:11.93Z"
        },
        {
            "id": 15032,
            "name": "Ubuntu 24",
            "maxCpu": null,
            "parentId": 15,
            "created": "2023-05-22T14:56:11.93Z"
        }
    ]
}

Затем, если я передам фильтр, подобный следующему:

["Windows 201", "Ubuntu 23"]

ответ должен быть:

{
    "count": 3,
    "results": [
        {
            "id": 15005,
            "name": "Windows 2012",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15027,
            "name": "Windows 2016",
            "maxCpu": null,
            "parentId": 15032,
            "created": "2021-02-16T08:17:45Z"
        },
        {
            "id": 15032,
            "name": "Ubuntu 23",
            "maxCpu": null,
            "parentId": 15,
            "created": "2023-05-22T14:56:11.93Z"
        }
    ]
}

Моя первая попытка использовать простой LinQ с лямбда-выражением была:

var licenses = await _repository.FindAll().Where(x => filter.Name.Contains(x.Name)).ToArrayAsync();

Проблема с этим запросом заключается в том, что он работает только с точными совпадениями (например: «Windows 2019» или «Ubuntu 23»), но не с частичными совпадениями.

Конечно, я мог бы просто выполнить итерацию с помощью цикла foreach и фильтровать каждый элемент один за другим, но мне хотелось бы найти более элегантное решение с использованием LinQ, если это возможно.

Какие-либо предложения?

Редактировать 1: Объяснение проблемы, описанной выше.

Говоря «мой пример запроса работает только с точными совпадениями», я пытаюсь сказать, что если я ищу, например, [«Windows 2016»] или, другой пример, [«Windows 2012», «Ubuntu 23»] это будет работать, но с ["Windows 20"] или ["Ubunbtu", "Windows"] нет, потому что совпадение неточное, и это то, что делает мой пример, просто работает с точными совпадениями, поэтому это работает частично.

Какая ORM и версия? Какой поставщик базы данных?

Svyatoslav Danyliv 29.07.2024 22:06

Я не понимаю, что вы имеете в виду под «Проблема этого запроса в том, что он работает только с точными совпадениями (например: «Windows 2019» или «Ubuntu 23»), но не с частичными совпадениями». Похоже, что ваш код, ваш текст в этом предложении и ваш пример в предложении несовместимы друг с другом.

Enigmativity 30.07.2024 00:27

Привет @Enigmativity, спасибо за ваши комментарии. Пожалуйста, ознакомьтесь с моими изменениями для получения дальнейших объяснений проблемы с моим примером запроса.

Diego Perez 30.07.2024 10:30

@DiegoPerez, ты не предоставил никакой дополнительной информации. Какую базу данных вы используете и какой ORM? LINQ не запускается сам по себе, его необходимо преобразовать в SQL. Если вы используете EF или NHibernate, этот «репозиторий» с одним объектом является большой ошибкой проектирования, поскольку ORM представляют собой абстракции более высокого уровня, чем классы CRUD. Это также означает, что, если FindAll() просто не вернет DbSet, на сам запрос будет влиять все, что делает этот метод.

Panagiotis Kanavos 30.07.2024 10:37

Базы данных @DiegoPerez не являются деталями реализации. Поиск в середине строки — самый медленный способ, поскольку он не может использовать индексы и требует полного сканирования таблицы. Однако для поиска слов можно использовать индексы полнотекстового поиска и другой синтаксис SQL. Это означает, что вам также нужен другой запрос LINQ. Поставщик LINQ SQL Server, NpgSql для PostgreSQL и Pomelo для MySQL предлагают функции FTS, такие как Содержит/FreeText для SQL Server, Match для MySQL, векторные совпадения для PostgreSQL. Какую базу данных вы используете?

Panagiotis Kanavos 30.07.2024 10:47

@DiegoPerez, с другой стороны, Windows 20 не требует содержания. Он может использовать BeginsWith, который преобразуется в title LIKE 'Windows 20% и может использовать обычные индексы, потому что по сути это поиск по диапазону, title >='Windows 20' and title <'Windows 21'

Panagiotis Kanavos 30.07.2024 10:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, использование .Any() здесь будет полезно.

var licenses = await _repository.FindAll().Where(x => filter.Name.Any(f => x.Name.Contains(f)).ToArrayAsync();

Поскольку filter.Name также является списком, он может использовать выражения LINQ. Кроме того, f теперь должен содержаться в x.Name.

Надеюсь, это поможет!

Даже если бы это можно было перевести на SQL, это было бы очень и очень медленно.

Panagiotis Kanavos 30.07.2024 10:48

Привет @TBold и спасибо, что уделил время. Я попробовал ваше решение, и оно сработало, как и ожидалось. Большой! :)

Diego Perez 30.07.2024 10:54

Другие вопросы по теме