Основы Solr и Elasticsearch с интеграцией в C#

Практический курс по изучению популярных поисковых движков с нуля до реализации поиска в .NET приложениях. Вы освоите индексацию, синтаксис запросов и взаимодействие с API через C#.

1. Введение в поисковые технологии: архитектура Lucene, установка и сравнение возможностей Solr и Elasticsearch

Введение в поисковые технологии: архитектура Lucene, установка и сравнение возможностей Solr и Elasticsearch

Добро пожаловать в курс «Основы Solr и Elasticsearch с интеграцией в C#». Если вы когда-либо пытались реализовать поиск на сайте, используя стандартные возможности SQL (например, оператор LIKE), вы наверняка сталкивались с проблемами производительности и релевантности. Запросы работают медленно на больших объемах данных, а результаты не всегда соответствуют ожиданиям пользователя. Здесь на сцену выходят специализированные поисковые движки.

В этой статье мы разберем фундамент современных поисковых систем — библиотеку Apache Lucene, сравним два самых популярных решения на её основе (Solr и Elasticsearch) и подготовим рабочее окружение для дальнейшей работы с C#.

Почему реляционные базы данных не подходят для поиска?

Реляционные базы данных (RDBMS), такие как PostgreSQL или MS SQL Server, отлично справляются с хранением структурированных данных и поддержанием их целостности. Однако они не оптимизированы для полнотекстового поиска.

Основные ограничения RDBMS в поиске:

  • Низкая скорость на больших текстах. Поиск подстроки требует сканирования всего текста.
  • Отсутствие лингвистического анализа. База данных не знает, что «бежал», «бегу» и «бег» — это слова с одним корнем.
  • Отсутствие ранжирования. SQL вернет все строки, где есть совпадение, но не скажет, какая из них наиболее релевантна запросу.
  • Для решения этих задач используется инвертированный индекс (Inverted Index) — структура данных, лежащая в основе Apache Lucene.

    Архитектура Apache Lucene

    Apache Lucene — это высокопроизводительная библиотека для полнотекстового поиска, написанная на Java. Важно понимать: Lucene — это не готовый сервер, к которому можно подключиться по HTTP. Это «мотор», который нужно встроить в приложение. Solr и Elasticsearch — это «автомобили», построенные вокруг этого мотора, предоставляющие удобный API и инструменты управления.

    Инвертированный индекс

    Представьте, что у нас есть три документа:

  • Документ 1: «C# — мощный язык»
  • Документ 2: «Solr использует Lucene»
  • Документ 3: «Elasticsearch тоже использует Lucene»
  • Чтобы найти документы, содержащие слово «Lucene», обычная база данных перебирала бы их по очереди. Lucene же заранее строит индекс, похожий на предметный указатель в конце книги.

    !Визуализация процесса создания инвертированного индекса из исходных документов

    Процесс создания индекса включает:

  • Токенизацию: Разбиение текста на отдельные слова (токены).
  • Нормализацию: Приведение слов к нижнему регистру, удаление окончаний (стемминг) или приведение к начальной форме (лемматизация).
  • Индексацию: Сохранение списка документов для каждого токена.
  • В итоге мы получаем структуру, где ключом является слово, а значением — список ID документов, где оно встречается.

    Релевантность и оценка (Scoring)

    Поисковые движки не просто находят документы, они сортируют их по релевантности. Долгое время стандартом был алгоритм TF-IDF, сейчас чаще используется его улучшенная версия — BM25. Рассмотрим базовую концепцию TF-IDF, чтобы понять принцип.

    Формула веса термина в документе выглядит следующим образом:

    Где: * — итоговый вес (важность) слова в документе . * (Term Frequency) — частота термина. Показывает, сколько раз слово встречается в конкретном документе . Чем чаще слово встречается в документе, тем оно важнее для этого документа. * (Inverse Document Frequency) — обратная частота документа. Показывает, насколько редким является слово во всей коллекции документов. Если слово встречается во всех документах (например, предлог «и»), его будет низким, и оно мало повлияет на поиск.

    Благодаря этой математике, уникальные слова, встречающиеся часто в конкретном документе, поднимают его выше в результатах поиска.

    Solr vs Elasticsearch: Битва титанов

    Оба инструмента используют Lucene, оба работают на Java, и оба предоставляют REST API. Но у них разные философии и области применения.

    Apache Solr

    Solr — ветеран рынка (появился в 2004 году). Он развивался как поисковая платформа для корпоративных порталов и сайтов электронной коммерции.

    * Преимущества: Огромное количество функций «из коробки», отличная работа с фасетным поиском (фильтры в интернет-магазинах), стабильность. * Управление: Традиционно использовал XML для конфигурации, но сейчас поддерживает и JSON API. Управляется через Zookeeper в кластерном режиме (SolrCloud). * Экосистема: Часто интегрируется с Hadoop.

    Elasticsearch

    Elasticsearch появился позже (в 2010 году) и сразу был ориентирован на облака, JSON и распределенную архитектуру.

    * Преимущества: Простота масштабирования, JSON-native (все запросы и ответы в JSON), часть стека ELK (Elasticsearch, Logstash, Kibana), что делает его стандартом де-факто для анализа логов и метрик. * Управление: Конфигурация через API, автоматическое перераспределение данных (шардов) при падении узлов. * Экосистема: Kibana — мощнейший инструмент визуализации данных.

    Сравнительная таблица

    | Характеристика | Apache Solr | Elasticsearch | | :--- | :--- | :--- | | Философия | Поиск для сайтов и Enterprise-систем | Аналитика, логи, поиск для приложений | | Формат данных | JSON, XML, CSV, Binary | JSON | | Масштабирование | Требует Zookeeper, сложнее в настройке | Встроено (Zen Discovery), проще | | Скорость индексации | Чуть медленнее (ориентир на точность) | Очень быстрая (Real-time search) | | Популярность | Стабильная, много Legacy проектов | Растущая, стандарт для новых проектов |

    Для C# разработчика Elasticsearch часто выглядит привлекательнее из-за нативного использования JSON, который легко мапится на POCO-классы (Plain Old CLR Objects).

    Основные понятия кластера

    Независимо от выбора, вам нужно знать терминологию архитектуры:

  • Node (Узел): Один экземпляр запущенного сервера (один процесс).
  • Cluster (Кластер): Группа узлов, работающих вместе.
  • Index (Индекс): Коллекция документов (аналог таблицы в SQL).
  • Shard (Шард): Часть индекса. Индекс можно разделить на несколько кусков (шардов), чтобы хранить их на разных узлах. Это позволяет масштабировать объем данных.
  • Replica (Реплика): Копия шарда. Нужна для отказоустойчивости (если узел с основным шардом упадет) и для ускорения чтения.
  • !Архитектура распределенного кластера с узлами и шардами

    Установка и запуск (Docker)

    Самый простой способ запустить эти системы без установки Java и настройки переменных среды — использовать Docker. Это идеально подходит для разработки и обучения.

    Запуск Elasticsearch

    Для локальной разработки мы запустим Elasticsearch в режиме одного узла (single-node), отключив сложные проверки безопасности, чтобы упростить старт.

    Выполните команду в терминале:

    * -p 9200:9200: Пробрасываем порт 9200 (стандартный порт для HTTP API). * discovery.type=single-node: Указываем, что кластер состоит из одной машины. * xpack.security.enabled=false: Отключаем логин/пароль (только для обучения!).

    Проверить работу можно в браузере или через curl:

    Вы должны получить JSON-ответ с информацией о версии.

    Запуск Solr

    Запустим Solr в стандартном режиме:

    * -p 8983:8983: Стандартный порт Solr.

    После запуска откройте в браузере админ-панель: http://localhost:8983. В отличие от Elasticsearch, у Solr есть полноценный графический интерфейс управления «из коробки».

    Взаимодействие через REST API

    В следующих статьях мы будем использовать C# для общения с этими сервисами. Однако важно понимать, что под капотом все библиотеки (NEST для Elastic или SolrNet для Solr) используют обычные HTTP-запросы.

    Пример того, как выглядит добавление документа в Elasticsearch через сырой HTTP:

    В ответ сервер вернет JSON с подтверждением создания и присвоенным ID.

    Резюме

    Мы разобрали, что Solr и Elasticsearch базируются на библиотеке Lucene и используют инвертированный индекс для быстрого поиска. Elasticsearch более популярен в современной разработке благодаря JSON-ориентированности и простоте масштабирования, тогда как Solr остается мощным инструментом для специфических задач электронной коммерции. Мы также развернули оба сервиса в Docker и убедились в их работоспособности.

    В следующей статье мы перейдем к практике и создадим наше первое консольное приложение на C# для подключения к Elasticsearch.

    2. Apache Solr: конфигурация схем, управление коллекциями и базовый синтаксис запросов через REST API

    Apache Solr: конфигурация схем, управление коллекциями и базовый синтаксис запросов через REST API

    В предыдущей статье мы развернули Apache Solr в Docker и убедились, что он запускается. Теперь пришло время превратить этот «пустой ящик» в рабочий инструмент. В отличие от реляционных баз данных, где вы сначала создаете таблицы через CREATE TABLE, а потом вставляете данные, поисковые движки имеют свою специфику определения структуры.

    Поскольку ваша конечная цель — взаимодействие с Solr через C#, важно понимать: Solr управляется через HTTP-запросы. Все, что мы будем делать в этой статье (создание индексов, настройка полей, поиск), в дальнейшем будет выполняться из вашего C# кода с помощью HttpClient. Сейчас мы разберем «сырые» запросы, чтобы вы понимали механику процесса.

    Иерархия данных в Solr

    Прежде чем писать запросы, нужно понять, как Solr хранит данные. В реляционных базах данных (SQL) у нас есть привычная цепочка: Сервер -> База данных -> Таблица -> Строка. В Solr иерархия выглядит иначе:

  • Cluster (Кластер): Совокупность всех узлов Solr.
  • Collection (Коллекция): Логический индекс, распределенный по серверам (аналог Таблицы, но более масштабный). В режиме Standalone (одиночный сервер) часто используется термин Core (Ядро).
  • Document (Документ): Единица информации (аналог Строки в таблице).
  • Field (Поле): Атрибут документа (аналог Колонки).
  • !Иерархическая структура данных в Solr: от кластера до поля документа

    Схема данных (Schema)

    Схема — это «конституция» вашего индекса. Она определяет, какие поля могут быть в документе и, самое главное, как эти поля должны обрабатываться перед записью.

    В современных версиях Solr используется Managed Schema. Это означает, что мы не редактируем XML-файлы вручную, а отправляем Solr команды через API: «Добавь поле», «Измени тип поля».

    Типы полей (Field Types)

    Тип поля определяет, как Solr «понимает» ваши данные. Это критически важно для поиска. Рассмотрим два основных типа:

  • String (string): Строка сохраняется «как есть». Если вы сохранили «Apple iPhone», то поиск по слову «iphone» (с маленькой буквы) ничего не найдет. Этот тип используется для ID, артикулов, категорий, тегов — там, где нужно точное совпадение для фильтрации.
  • Text (text_general): Текст проходит анализ (tokenization). Фраза «Apple iPhone» разбивается на токены [apple, iphone], приводится к нижнему регистру. Поиск «iphone» успешно найдет этот документ. Используется для названий, описаний и полнотекстового поиска.
  • Атрибуты полей

    При объявлении поля мы задаем атрибуты, которые управляют его поведением:

    * indexed=true: Поле попадает в поисковый индекс. По нему можно искать, фильтровать и сортировать. Если поставить false, поле будет просто храниться, но найти по нему ничего нельзя. * stored=true: Значение поля хранится в исходном виде и возвращается в результатах поиска. Если поставить false, вы сможете найти документ по этому полю, но само поле в ответе JSON не увидите (полезно для огромных текстов, которые нужны только для поиска). * multiValued=true: Поле может содержать массив значений (например, список тегов или категорий). * required=true: Документ будет отклонен, если в нем нет этого поля.

    Практика: Работа через REST API

    Давайте перейдем к практике. Мы будем использовать формат запросов, который вы позже сможете перенести в C#. Представим, что мы создаем каталог электроники.

    Шаг 1. Создание коллекции (Core)

    Для начала создадим место для хранения данных. В Solr API для этого используется CoreAdmin API.

    HTTP запрос: GET http://localhost:8983/solr/admin/cores?action=CREATE&name=electronics&configSet=_default

    * name=electronics: Имя нашей коллекции. * configSet=_default: Использование стандартной конфигурации.

    Шаг 2. Настройка схемы (Schema API)

    По умолчанию Solr пытается «угадать» типы полей, но для серьезной разработки схему нужно задавать явно. Добавим поля для наших товаров.

    Мы отправляем POST запрос на адрес http://localhost:8983/solr/electronics/schema с JSON-телом:

    Здесь мы создали: * product_name: для полнотекстового поиска. * category: для точной фильтрации (строгий строковый тип). * price: число с плавающей точкой для сортировки и диапазонов.

    Шаг 3. Индексация документов

    Теперь добавим данные. В Solr это называется индексацией.

    POST запрос на http://localhost:8983/solr/electronics/update?commit=true.

    > Параметр commit=true заставляет Solr сразу же сохранить изменения на диск и сделать их доступными для поиска. В высоконагруженных системах коммиты делают реже (автоматически), но для тестов это удобно.

    Тело запроса (JSON):

    Шаг 4. Базовый поиск

    Самое интересное — получение данных. Основной эндпоинт для поиска: /select.

    Пример 1: Найти всё GET http://localhost:8983/solr/electronics/select?q=:

    * q (Query): Параметр запроса. :*: Синтаксис «найти все документы» (матчить любое поле по любому значению).

    Пример 2: Полнотекстовый поиск GET http://localhost:8983/solr/electronics/select?q=product_name:iphone

    Solr найдет документ с ID 1, так как поле product_name имеет тип text_general и слово «iPhone» было проиндексировано в нижнем регистре.

    Пример 3: Фильтрация (Filter Query) GET http://localhost:8983/solr/electronics/select?q=:&fq=price:[300 TO 1000]

    * fq (Filter Query): Фильтр ограничивает выборку, но не влияет на релевантность (счет) документа. Фильтры работают быстрее и кэшируются Solr. * price:[300 TO 1000]: Диапазон цен от 300 до 1000 включительно.

    Пример 4: Сортировка и пагинация GET http://localhost:8983/solr/electronics/select?q=:&sort=price desc&rows=10&start=0

    * sort: Поле и направление (asc — по возрастанию, desc — по убыванию). * rows: Количество документов на странице. * start: Смещение (сколько документов пропустить).

    Связь с C#

    Вы можете спросить: «Зачем мне знать URL-адреса, если я пишу на C#?».

    Дело в том, что популярные библиотеки для Solr в .NET (например, SolrNet) под капотом делают именно эти HTTP-запросы. А если вы решите не использовать тяжелые библиотеки, вы будете использовать стандартный HttpClient.

    Ваш код на C# будет выглядеть концептуально так:

    Понимание того, как формируется URL и тело запроса, позволит вам отлаживать любые проблемы, просто скопировав запрос из кода в браузер или Postman.

    Заключение

    Мы разобрали фундамент Solr: иерархию данных, важность схемы и базовые операции CRUD (Create, Read, Update, Delete) через REST API. Правильно настроенная схема — это 80% успеха быстрого поиска.

    В следующих уроках мы углубимся в сложные запросы, фасетный поиск (категоризацию) и начнем писать реальный код на C# для автоматизации этих действий.

    3. Elasticsearch: работа с JSON-документами, маппинг, Query DSL и агрегации данных

    Elasticsearch: работа с JSON-документами, маппинг, Query DSL и агрегации данных

    В предыдущих статьях мы познакомились с архитектурой Lucene, запустили Elasticsearch в Docker и изучили основы Solr. Теперь пришло время погрузиться в мир Elasticsearch. Если Solr часто воспринимается как строгая система с XML-конфигурациями, то Elasticsearch — это гибкий, JSON-ориентированный движок, который «говорит» на языке современных веб-разработчиков.

    В этой статье мы разберем жизненный цикл документа, научимся правильно определять типы данных (маппинг), освоим мощный язык запросов Query DSL и узнаем, как превратить поисковый движок в аналитическую платформу с помощью агрегаций.

    Документ как основа данных

    В реляционных базах данных (SQL) мы привыкли мыслить строками и столбцами. В Elasticsearch единицей информации является JSON-документ. Это иерархическая структура данных, которая содержит поля и значения.

    Когда вы отправляете JSON в Elasticsearch, он сохраняется в специальном системном поле _source. Однако для того, чтобы поиск работал быстро, Elasticsearch должен разобрать этот JSON и построить инвертированный индекс.

    Структура документа

    Каждый документ при индексации получает метаданные:

    * _index: Имя индекса (аналог таблицы), где лежит документ. * _id: Уникальный идентификатор документа (можно задать вручную или сгенерировать автоматически). * _version: Номер версии (используется для контроля конкурентного доступа). * _seq_no и _primary_term: Служебные поля для синхронизации реплик.

    Маппинг (Mapping): Схема данных

    Маппинг — это процесс определения того, как документ и его поля сохраняются и индексируются. Это аналог схемы (Schema) в базе данных.

    Существует два подхода к маппингу:

  • Динамический маппинг: Вы просто отправляете JSON, а Elasticsearch сам угадывает типы полей. Например, если он видит "price": 100, он создаст поле типа long. Это удобно для старта, но опасно для продакшена (можно случайно засорить индекс неправильными типами).
  • Явный маппинг (Explicit Mapping): Вы заранее описываете структуру индекса. Это рекомендуемый подход.
  • Text vs Keyword

    Самая важная концепция, которую нужно понять C# разработчику при переходе на Elasticsearch — это различие между двумя строковыми типами: text и keyword.

    !Визуализация различия обработки типов text и keyword: text разбивается на слова для поиска, keyword сохраняется целиком для точных совпадений.

    * Text: Используется для полнотекстового поиска. Содержимое поля проходит через анализатор (разбивается на слова, приводится к нижнему регистру). По такому полю нельзя делать точную сортировку или агрегацию. * Keyword: Используется для точных совпадений, сортировки и агрегаций. Содержимое сохраняется «как есть». Идеально для ID, статусов (published, draft), email-адресов или тегов.

    Пример создания индекса с явным маппингом через REST API:

    Query DSL: Язык запросов

    Elasticsearch предоставляет мощный JSON-интерфейс для поиска, называемый Query DSL (Domain Specific Language). Запросы делятся на две большие группы: Leaf queries (листовые запросы) и Compound queries (составные запросы).

    Листовые запросы (Leaf Queries)

    Они ищут конкретное значение в конкретном поле.

  • Match query: Стандартный запрос для полнотекстового поиска. Он анализирует строку запроса.
  • Этот запрос найдет документы, содержащие «ноутбук» ИЛИ «gaming» (или оба), даже если они написаны с большой буквы.

  • Term query: Точный поиск. Не анализирует запрос. Ищет точное совпадение в инвертированном индексе.
  • Важно: Никогда не используйте term для полей типа text. Это частая ошибка новичков.

  • Range query: Поиск по диапазону (числа, даты).
  • Составные запросы (Compound Queries)

    Самый популярный составной запрос — bool. Он позволяет комбинировать листовые запросы, используя логику булевой алгебры. Внутри bool есть четыре секции:

    * must: Документ обязан соответствовать условию (влияет на релевантность/score). * filter: Документ обязан соответствовать условию (не влияет на score, работает быстрее, кэшируется). Используйте для точных фильтров (цена, категория). * should: Документ может соответствовать (если соответствует — score выше). Аналог оператора OR. * must_not: Документ не должен соответствовать.

    Пример сложного запроса:

    Этот запрос означает: «Найди мне электронику (фильтр) дороже 500 (фильтр), где в названии есть слово 'ноутбук' (must). Если это Apple — подними в выдаче выше (should)».

    Агрегации: Аналитика данных

    Elasticsearch — это не только поиск, но и мощный аналитический движок. Агрегации позволяют собирать статистику по данным. Это похоже на GROUP BY в SQL, но намного гибче.

    Агрегации делятся на:

  • Bucket aggregations (Ведра): Группируют документы в «корзины» (например, по категориям).
  • Metric aggregations (Метрики): Вычисляют значения на основе документов в корзине (среднее, сумма, мин/макс).
  • Пример: Средняя цена по категориям

    Допустим, мы хотим узнать среднюю цену товаров в каждой категории. Для вычисления среднего значения используется формула среднего арифметического:

    Где: * — среднее значение (результат агрегации avg). * — количество документов в выборке (count). * — значение поля (в нашем случае price) для -го документа. * — знак суммирования всех значений.

    Запрос будет выглядеть так:

    * "size": 0: Нам не нужны сами документы, только цифры. * terms: Создает «ведра» для каждой уникальной категории. * avg: Считает среднее внутри каждого ведра.

    Заключение

    Мы разобрали фундамент работы с Elasticsearch: от структуры JSON-документа до сложных аналитических запросов. Понимание разницы между text и keyword, а также умение комбинировать must и filter в bool запросах — это 80% успеха при работе с этой системой.

    В следующей статье мы наконец перейдем к практике на C# и подключим библиотеку NEST (или новый Elastic.Clients.Elasticsearch), чтобы выполнять все эти операции из кода вашего приложения.

    > Документация — лучший друг разработчика. Всегда сверяйтесь с официальным справочником Elasticsearch при проектировании сложных запросов.

    4. Интеграция Solr с C#: реализация HTTP-клиента и использование библиотеки SolrNet

    Интеграция Solr с C#: реализация HTTP-клиента и использование библиотеки SolrNet

    В предыдущих статьях мы развернули Apache Solr, изучили его схему данных и научились выполнять запросы через curl и браузер. Теперь пришло время автоматизировать этот процесс. В реальных приложениях пользователи не пишут запросы в адресной строке — они вводят текст в поле поиска, а ваше приложение на C# отправляет запрос к Solr и отображает результаты.

    В этой статье мы рассмотрим два способа взаимодействия с Solr:

  • Низкоуровневый: Использование стандартного HttpClient для отправки «сырых» JSON-запросов.
  • Высокоуровневый: Использование популярной библиотеки SolrNet, которая берет на себя всю рутину по сериализации и построению запросов.
  • Архитектура взаимодействия

    Прежде чем писать код, важно понять, как наше приложение общается с поисковым движком. Solr предоставляет REST API. Это значит, что любое взаимодействие — это HTTP-запрос (GET, POST, DELETE).

    !Визуализация процесса обмена данными между приложением и Solr через протокол HTTP

    Способ 1: Использование HttpClient

    Этот способ полезен для понимания того, что происходит «под капотом», или если вам нужно выполнить специфический запрос, который не поддерживается готовыми библиотеками.

    Подготовка проекта

    Создайте новое консольное приложение:

    Нам понадобится библиотека для работы с JSON. В современных версиях .NET используется System.Text.Json.

    Реализация поиска

    Предположим, у нас есть коллекция tech_products, и мы хотим найти товары по названию. URL для поиска выглядит так: http://localhost:8983/solr/tech_products/select?q=name:iphone.

    Вот пример кода на C#, который выполняет этот запрос:

    Пагинация

    Для реализации постраничного вывода (пагинации) нам нужно правильно рассчитать параметр Start (смещение). Формула расчета смещения выглядит так:

    Где: * — значение Start (смещение), то есть сколько записей нужно пропустить. * — номер текущей страницы (начиная с 1). * — количество записей на странице (Rows).

    Например, для 3-й страницы при отображении 10 элементов: . Мы пропускаем первые 20 записей и показываем следующие 10.

    Удаление данных

    Удалять можно по ID или по запросу.

    Обработка ошибок

    При работе с SolrNet важно перехватывать исключения SolrConnectionException. Они возникают, если сервер Solr недоступен или вернул ошибку (например, 400 Bad Request из-за неверного синтаксиса запроса).

    Сравнение подходов

    | Характеристика | HttpClient | SolrNet | | :--- | :--- | :--- | | Сложность кода | Высокая (много ручной работы) | Низкая (готовые методы) | | Типизация | Отсутствует (работа с JSON строками) | Строгая (POCO классы) | | Гибкость | Максимальная | Ограничена возможностями библиотеки | | Поддержка DI | Требует ручной настройки | Встроена |

    Заключение

    Мы научились интегрировать Solr в приложение на C#. Использование библиотеки SolrNet значительно упрощает разработку, предоставляя типизированный интерфейс для операций CRUD и поиска. Мы также разобрали, как мапить поля Solr на классы C# с помощью атрибутов и как управлять пагинацией.

    В следующей статье мы перейдем к конкуренту Solr и рассмотрим, как подключить Elasticsearch к .NET приложению, используя официальный клиент.

    5. Взаимодействие C# и Elasticsearch: работа через LowLevel клиент и высокоуровневую библиотеку NEST

    Взаимодействие C# и Elasticsearch: работа через LowLevel клиент и высокоуровневую библиотеку NEST

    В предыдущих статьях мы изучили теорию Elasticsearch, научились писать JSON-запросы и даже сравнили этот процесс с работой в Solr. Мы также рассмотрели, как интегрировать Solr в C# с помощью SolrNet. Теперь пришло время соединить мощь Elasticsearch с экосистемой .NET.

    В мире .NET для работы с Elasticsearch исторически сложилась двухуровневая архитектура клиентов. Понимание разницы между ними критически важно для построения производительных приложений.

    Архитектура клиентов Elasticsearch для .NET

    В отличие от SolrNet, который является единой библиотекой, официальная поддержка Elasticsearch для .NET (до 8-й версии включительно) разделена на два слоя:

  • Elasticsearch.Net (LowLevel Client): Это низкоуровневый драйвер. Он не знает о ваших классах и типах данных. Он работает со строками, байтами и потоками (Stream). Его задача — эффективно управлять HTTP-соединениями, балансировкой нагрузки и переключением при сбоях (failover).
  • NEST (HighLevel Client): Это высокоуровневая обертка над LowLevel клиентом. Она добавляет строгую типизацию, маппинг объектов (POCO) в JSON и предоставляет мощный DSL (Domain Specific Language) для написания запросов в стиле LINQ.
  • !Схематичное изображение архитектуры взаимодействия приложения с Elasticsearch через два уровня клиентов

    Подготовка проекта

    Создадим новое консольное приложение и установим необходимые пакеты. Мы будем использовать библиотеку NEST, так как она является стандартом для большинства существующих корпоративных систем.

    > Важно: Библиотека NEST (версии 7.x) является наиболее стабильной и функциональной. Для Elasticsearch 8.x существует новый клиент Elastic.Clients.Elasticsearch, но NEST по-прежнему широко используется благодаря своей зрелости.

    Работа с LowLevel клиентом (Elasticsearch.Net)

    Начнем с низкого уровня. Это полезно, когда вам нужна максимальная производительность и вы хотите избежать накладных расходов на сериализацию, или если вы мигрируете старые запросы и у вас уже есть готовые JSON-строки.

    LowLevel клиент входит в состав пакета NEST, но его можно использовать отдельно.

    Подключение и отправка JSON

    Поиск с использованием Fluent API

    Самая мощная часть NEST — это Fluent API, который повторяет структуру Query DSL. Вспомним запрос bool / must / filter из прошлой статьи и реализуем его на C#.

    Задача: Найти товары категории "computers" (фильтр), в названии которых есть "gaming" (поиск), и отсортировать по цене.

    Обратите внимание на использование лямбда-выражений t => t.Category. Это обеспечивает проверку типов на этапе компиляции. Если вы переименуете свойство в классе Product, код перестанет компилироваться, что защищает от ошибок.

    Пагинация

    В примере выше мы использовали методы .From() и .Size(). Логика расчета смещения здесь такая же, как и в Solr, и подчиняется формуле:

    Где: * — значение параметра From (смещение, offset). * — номер текущей страницы, которую запрашивает пользователь. * — количество элементов на одной странице (параметр Size).

    Например, для получения 3-й страницы по 20 элементов: . Мы пропускаем 40 документов.

    Агрегации в NEST

    Реализуем пример из прошлой статьи: вычисление средней цены по категориям. В JSON это выглядело как вложенность aggs -> terms -> aggs -> avg. В C# структура идентична.

    Обработка ошибок и отладка

    Одна из самых полезных функций NEST — свойство DebugInformation. Если запрос не работает, и вы не понимаете почему, выведите это свойство в консоль.

    Оно покажет:

  • Успешность запроса.
  • Оригинальный JSON, который NEST отправил на сервер.
  • Ответ сервера или текст ошибки (Exception).
  • Это незаменимый инструмент при разработке сложных запросов.

    Сравнение SolrNet и NEST

    Теперь, когда мы изучили обе библиотеки, подведем итоги:

    | Характеристика | SolrNet (Solr) | NEST (Elasticsearch) | | :--- | :--- | :--- | | Стиль запросов | Объектный (создание экземпляров классов Query) | Fluent API (цепочки лямбда-выражений) | | Маппинг | Атрибуты [SolrField] | Автоматический или через атрибуты | | Уровень абстракции | Средний | Очень высокий (скрывает почти весь JSON) | | Документация | Хорошая, но сообщество меньше | Огромное сообщество, отличная документация |

    Заключение

    Мы научились подключать C# к Elasticsearch, используя два подхода: быстрый LowLevel клиент для простых задач и мощный NEST для построения сложных типизированных запросов. Мы перенесли знания о Query DSL и агрегациях из формата JSON в код C#.

    В следующей части курса мы займемся продвинутыми темами: настройкой анализаторов текста и реализацией автодополнения (Autocomplete), что является "золотым стандартом" современного поиска.