Подключение полнотекстового поиска из Elasticsearch в ASP.NET Core
Elasticsearch - это распределённый поисковый и аналитический движок, построенный поверх Apache Lucene. В контексте backend-разработки он чаще всего используется не как база данных общего назначения, а как специализированный сервис для:
- полнотекстового поиска по большим объёмам данных;
- быстрого поиска с релевантным ранжированием;
- аналитики и агрегаций по текстовым и структурированным данным;
- поиска по логам, событиям и time-series данным.
Типичные сценарии использования в ASP.NET Core:
- поиск по статьям, товарам, документам;
- автодополнение и fuzzy-поиск;
- поиск с фильтрами и сортировками;
- вынесение «тяжёлого» поиска из основной БД (SQL).
В этой статье мы рассмотрим минимальное, но рабочее подключение Elasticsearch для полнотекстового поиска в ASP.NET Core без усложнения архитектуры.
Данный пример написан для официального .NET клиента - библиотеки Elastic.Clients.Elasticsearch версии 9.x.
Архитектура примера
Архитектура намеренно упрощена:
- ASP.NET Core приложение
- Один сервис для работы с Elasticsearch
- Один индекс с текстовыми документами
- REST-контроллер
Без брокеров и без фоновых сервисов.
Практический пример
Установка пакета
Добавьте NuGet-пакет (через консоль диспетчера пакетов)
Install-Package Elastic.Clients.Elasticsearch
Модель документа
public class ArticleDocument
{
public int Id { get; set; }
// Основное поле для полнотекстового поиска
public string Content { get; set; } = string.Empty;
}
Конфигурация клиента Elasticsearch
using Elastic.Clients.Elasticsearch;
var settings = new ElasticsearchClientSettings(new Uri("http://localhost:9200"))
.Authentication(new BasicAuthentication("elastic", "elastic_password"))
.DefaultIndex("articles");
// Клиент регистрируется как Singleton
builder.Services.AddSingleton(new ElasticsearchClient(settings));
Ключевые моменты:
DefaultIndexупрощает вызовы клиента- Клиент потокобезопасен и должен жить как Singleton
- Здесь нет проверок доступности Elasticsearch - это упрощение
Сервис работы с Elasticsearch
using Elastic.Clients.Elasticsearch;
public class ArticleSearchService
{
private readonly ElasticsearchClient _client;
public ArticleSearchService(ElasticsearchClient client)
{
_client = client;
}
public async Task IndexAsync(IEnumerable<ArticleDocument> documents)
{
// Bulk-индексация - предпочтительный способ записи
var response = await _client.BulkAsync(b => b
.Index("articles")
.IndexMany(documents)
);
if (response.Errors)
{
throw new InvalidOperationException("Ошибка при индексации документов");
}
}
public async Task<IReadOnlyCollection<ArticleDocument>> SearchAsync(string query)
{
var response = await _client.SearchAsync<ArticleDocument>(s => s
.Indices("articles")
.Query(q => q
.Match(m => m
.Field(f => f.Content)
.Query(query)
)
)
);
return response.Documents;
}
}
Ключевые моменты:
- Используется
BulkAsync, а не одиночная индексация - Match-запрос - базовый вариант полнотекстового поиска
- Ошибки bulk-запроса нужно проверять явно
Регистрация сервиса:
builder.Services.AddScoped<ArticleSearchService>();
Контроллер
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/search")]
public class SearchController : ControllerBase
{
private readonly ArticleSearchService _service;
public SearchController(ArticleSearchService service)
{
_service = service;
}
[HttpGet("index")]
public async Task<IActionResult> Index()
{
var documents = new[]
{
new ArticleDocument { Id = 1, Content = "Моя первая статья по ASP.NET Core и Elasticsearch" },
new ArticleDocument { Id = 2, Content = "Полнотекстовый поиск в .NET" },
new ArticleDocument { Id = 3, Content = "Работа с Elasticsearch 9 - быстрый старт" }
};
await _service.IndexAsync(documents);
return Ok();
}
[HttpGet]
public async Task<IActionResult> Search([FromQuery] string q)
{
var result = await _service.SearchAsync(q);
return Ok(result);
}
}
URL-ы:
GET /api/search/index- загрузка данныхGET /api/search?q=Elasticsearch- поиск
Инфраструктура
docker-compose.yml
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.2.3
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=elastic_password
- xpack.security.http.ssl.enabled=false
- xpack.security.transport.ssl.enabled=false
- ES_JAVA_OPTS=-Xms1g -Xmx1g
ports:
- "9200:9200"
volumes:
- esdata:/usr/share/elasticsearch/data
ulimits:
memlock:
soft: -1
hard: -1
networks:
- elastic
kibana:
image: docker.elastic.co/kibana/kibana:9.2.3
container_name: kibana
depends_on:
- elasticsearch
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=kibana_system
- ELASTICSEARCH_PASSWORD=kibana_password
ports:
- "5601:5601"
networks:
- elastic
volumes:
esdata:
networks:
elastic:
driver: bridge
Для корректного запуска kibana при первом запуске Elasticsearch требуется указать пароль системного пользователя kibana_system с помощью команды:
docker exec -it elasticsearch bin/elasticsearch-reset-password -u kibana_system -i
Проверка работы
- Запустите Elasticsearch (локально или через Docker)
- Запустите ASP.NET Core приложение
- Выполните GET
/api/search/index - Дождитесь успешного ответа
- Выполните GET
/api/search?q=elasticsearch - Убедитесь, что документы найдены
Для запроса elasticsearch должен быть такой результат:
[
{ "id": 3, "content": "Работа с Elasticsearch 9 - быстрый старт" },
{ "id": 1, "content": "Моя первая статья по ASP.NET Core и Elasticsearch" }
]
Если поиск ничего не возвращает:
- проверьте, что индекс создан
- учтите NRT-задержку (refresh)
Методические ремарки
Упрощения примера:
- нет схемы индекса (mappings)
- нет обработки refresh
- нет retries и логирования
Для production:
- явные mappings и analyzers
- управление lifecycle индексов
- timeout и cancellation token
- health-check Elasticsearch
- DTO для API
Типичные ошибки новичков:
- использование Elasticsearch как primary DB
- индексация «по одному документу"
- отсутствие контроля версий API
Заключение
В этой статье мы:
- подключили Elasticsearch 9 к ASP.NET Core
- реализовали индексацию и полнотекстовый поиск
- разобрали ключевые архитектурные решения
Дальнейшие шаги:
- analyzers и stemming
- фильтры и сортировки
- autocomplete и suggesters
- интеграция с реальной БД
Пример минимален, но отражает реальную точку входа в Elasticsearch для .NET-разработчика.