У меня есть запрос «Лицензии», который возвращает объект 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"] нет, потому что совпадение неточное, и это то, что делает мой пример, просто работает с точными совпадениями, поэтому это работает частично.
Я не понимаю, что вы имеете в виду под «Проблема этого запроса в том, что он работает только с точными совпадениями (например: «Windows 2019» или «Ubuntu 23»), но не с частичными совпадениями». Похоже, что ваш код, ваш текст в этом предложении и ваш пример в предложении несовместимы друг с другом.
Привет @Enigmativity, спасибо за ваши комментарии. Пожалуйста, ознакомьтесь с моими изменениями для получения дальнейших объяснений проблемы с моим примером запроса.
@DiegoPerez, ты не предоставил никакой дополнительной информации. Какую базу данных вы используете и какой ORM? LINQ не запускается сам по себе, его необходимо преобразовать в SQL. Если вы используете EF или NHibernate, этот «репозиторий» с одним объектом является большой ошибкой проектирования, поскольку ORM представляют собой абстракции более высокого уровня, чем классы CRUD. Это также означает, что, если FindAll() просто не вернет DbSet, на сам запрос будет влиять все, что делает этот метод.
Базы данных @DiegoPerez не являются деталями реализации. Поиск в середине строки — самый медленный способ, поскольку он не может использовать индексы и требует полного сканирования таблицы. Однако для поиска слов можно использовать индексы полнотекстового поиска и другой синтаксис SQL. Это означает, что вам также нужен другой запрос LINQ. Поставщик LINQ SQL Server, NpgSql для PostgreSQL и Pomelo для MySQL предлагают функции FTS, такие как Содержит/FreeText для SQL Server, Match для MySQL, векторные совпадения для PostgreSQL. Какую базу данных вы используете?
@DiegoPerez, с другой стороны, Windows 20 не требует содержания. Он может использовать BeginsWith, который преобразуется в title LIKE 'Windows 20% и может использовать обычные индексы, потому что по сути это поиск по диапазону, title >='Windows 20' and title <'Windows 21'





Я думаю, использование .Any() здесь будет полезно.
var licenses = await _repository.FindAll().Where(x => filter.Name.Any(f => x.Name.Contains(f)).ToArrayAsync();
Поскольку filter.Name также является списком, он может использовать выражения LINQ. Кроме того, f теперь должен содержаться в x.Name.
Надеюсь, это поможет!
Даже если бы это можно было перевести на SQL, это было бы очень и очень медленно.
Привет @TBold и спасибо, что уделил время. Я попробовал ваше решение, и оно сработало, как и ожидалось. Большой! :)
Какая ORM и версия? Какой поставщик базы данных?