У меня есть запрос «Лицензии», который возвращает объект 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 и версия? Какой поставщик базы данных?