Использование Azure.Search.Documents в приложении .NET

Примечание.

Поиск с использованием ИИ Azure доступна через портал Azure, REST API и Azure SDKs. Он также лежит в основе Foundry IQ — управляемого слоя знаний, который преобразует корпоративный контент в многократно используемые базы знаний с учетом разрешений доступа для агентов на портале Microsoft Foundry.

В этой статье объясняется, как создавать и управлять объектами поиска с помощью C# и Azure. Клиентская библиотека Search.Documents в Azure SDK для .NET.

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

  • Создание поисковых индексов, источников данных, индексаторов, наборов навыков и сопоставлений синонимов и управление ими
  • Загрузка документов поиска в индекс и управление ими
  • Выполнение запросов без необходимости вникать в особенности работы HTTP и JSON
  • Запуск и управление обогащением с помощью ИИ (наборами навыков) и выходными данными

Библиотека распространяется как один пакет NuGet, включающий все API, используемые для программного доступа к службе поиска.

Клиентская библиотека определяет такие классы, как SearchIndex, SearchField и SearchDocument, а также такие операции, как SearchIndexClient.CreateIndex и SearchClient.Search в классах SearchIndexClient и SearchClient. Эти классы упорядочены в следующие подразделы:

Azure. Клиентская библиотека Search.Documents не предоставляет операции управления службами, например создание и масштабирование служб поиска и управление ключами API. Если вам нужно управлять ресурсами поиска в приложении .NET, используйте библиотеку Azure.ResourceManager.Search в Azure SDK для .NET.

Требования к пакету SDK

  • Visual Studio 2019 или более поздней версии.

  • Поиск с использованием ИИ Azure

  • Скачайте пакет NuGet с помощью Сервис>Диспетчер пакетов NuGet>Управление пакетами NuGet для решения в Visual Studio. Выполните поиск по имени пакета Azure.Search.Documents.

Пакет SDK Azure для .NET соответствует .NET Standard 2.0.

Пример приложения

В этой статье показано, как использовать пример кода DotNetHowTo на сайте GitHub, чтобы проиллюстрировать основные понятия в поиске ИИ Azure, а также как создавать, загружать и запрашивать индекс поиска.

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

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

// This sample shows how to delete, create, upload documents and query an index
static void Main(string[] args)
{
    IConfigurationBuilder builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
    IConfigurationRoot configuration = builder.Build();

    SearchIndexClient indexClient = CreateSearchIndexClient(configuration);

    string indexName = configuration["SearchIndexName"];

    Console.WriteLine("{0}", "Deleting index...\n");
    DeleteIndexIfExists(indexName, indexClient);

    Console.WriteLine("{0}", "Creating index...\n");
    CreateIndex(indexName, indexClient);

    SearchClient searchClient = indexClient.GetSearchClient(indexName);

    Console.WriteLine("{0}", "Uploading documents...\n");
    UploadDocuments(searchClient);

    SearchClient indexClientForQueries = CreateSearchClientForQueries(indexName, configuration);

    Console.WriteLine("{0}", "Run queries...\n");
    RunQueries(indexClientForQueries);

    Console.WriteLine("{0}", "Complete.  Press any key to end application...\n");
    Console.ReadKey();
}

Ниже приведен частичный снимок экрана выходных данных, где предполагается, что вы запустили это приложение с действительным именем службы и ключами API:

Снимок экрана: выходные данные Console.WriteLine из примера программы.

Типы клиентов

В клиентской библиотеке используется три типа клиентов для выполнения различных операций: SearchIndexClient для создания, обновления и удаления индексов, SearchClient для загрузки или запроса индекса, а также SearchIndexerClient для работы с индексаторами и навыками. В этой статье рассматриваются первые два.

Всем клиентам требуются как минимум имя службы или конечная точка, а также ключ API. Обычно эти сведения предоставляются в файле конфигурации, аналогично тому, что вы найдете в файле appsettings.json примера приложения DotNetHowTo. Для чтения данных из файла конфигурации добавьте в программу using Microsoft.Extensions.Configuration;.

Следующая инструкция создает клиент индекса, используемый для создания, обновления и удаления индексов. Он принимает конечную точку службы и ключ API администратора.

private static SearchIndexClient CreateSearchIndexClient(IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["YourSearchServiceEndPoint"];
    string adminApiKey = configuration["YourSearchServiceAdminApiKey"];

    SearchIndexClient indexClient = new SearchIndexClient(new Uri(searchServiceEndPoint), new AzureKeyCredential(adminApiKey));
    return indexClient;
}

Следующий оператор создает клиент поиска, который используется для загрузки документов или выполнения запросов. Для SearchClient требуется индекс. Для загрузки документов требуется ключ API администратора, но для выполнения запросов можно использовать ключ API запросов.

string indexName = configuration["SearchIndexName"];

private static SearchClient CreateSearchClientForQueries(string indexName, IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["YourSearchServiceEndPoint"];
    string queryApiKey = configuration["YourSearchServiceQueryApiKey"];

    SearchClient searchClient = new SearchClient(new Uri(searchServiceEndPoint), indexName, new AzureKeyCredential(queryApiKey));
    return searchClient;
}

Примечание.

Если вы предоставляете недопустимый ключ для операции импорта (например, ключ запроса, в котором требуется ключ администратора), SearchClient вызывает CloudException сообщение об ошибке "Запрещено " при первом вызове метода операции. В этом случае проверьте ключ API еще раз.

Удалить индекс.

На ранних стадиях разработки может потребоваться добавить инструкцию DeleteIndex для удаления рабочего индекса, чтобы затем создать его повторно с обновленным определением. Пример кода для поиска ИИ Azure часто включает шаг удаления, чтобы повторно запустить пример.

В следующей строке вызывается DeleteIndexIfExists:

Console.WriteLine("{0}", "Deleting index...\n");
DeleteIndexIfExists(indexName, indexClient);

Этот метод использует заданный SearchIndexClient, чтобы проверить наличие индекса и удалить его, если он существует.

private static void DeleteIndexIfExists(string indexName, SearchIndexClient indexClient)
{
    try
    {
        if (indexClient.GetIndex(indexName) != null)
        {
            indexClient.DeleteIndex(indexName);
        }
    }
    catch (RequestFailedException e) when (e.Status == 404)
    {
        // Throw an exception if the index name isn't found
        Console.WriteLine("The index doesn't exist. No deletion occurred.");

Примечание.

В примере кода в этой статье для простоты используются синхронные методы, но в реальных приложениях рекомендуем вам применять для масштабируемости и скорости асинхронные методы. Например, в предыдущем методе можно было использовать DeleteIndexAsync вместо DeleteIndex.

Создание индекса

Для создания индекса можно использовать SearchIndexClient.

Следующий метод создает новый SearchIndex объект со списком объектов, определяющих SearchField схему нового индекса. Каждое поле имеет имя, тип данных и несколько атрибутов, которые определяют его поведение при поиске.

Поля можно определить из класса модели с помощью FieldBuilder. Класс FieldBuilder использует отражение, чтобы создать список объектов SearchField для соответствующего индекса. Он проверяет общедоступные свойства и атрибуты полученного класса модели Hotel. Класс Hotel мы рассмотрим подробно позже.

private static void CreateIndex(string indexName, SearchIndexClient indexClient)
{
    FieldBuilder fieldBuilder = new FieldBuilder();
    var searchFields = fieldBuilder.Build(typeof(Hotel));

    var definition = new SearchIndex(indexName, searchFields);

    indexClient.CreateOrUpdateIndex(definition);
}

Помимо полей, вы также можете добавить в индекс профили оценивания, средства подбора или параметры CORS (для краткости они не включены в пример). Дополнительные сведения об объекте SearchIndex и его составных частях можно найти в списке свойств SearchIndex, а также в справочнике по REST API.

Примечание.

При необходимости вы всегда можете создать список объектов Field напрямую, а не с помощью FieldBuilder. Например, можно не использовать класс модели или использовать существующий класс модели, который не требуется изменять путем добавления атрибутов.

Вызовите CreateIndex в Main()

Main создает новый индекс отелей путем вызова предыдущего метода:

Console.WriteLine("{0}", "Creating index...\n");
CreateIndex(indexName, indexClient);

Использование класса модели для представления данных

В примере DotNetHowTo для структур данных Hotel (Отель), Address (Адрес) и Room (Номер) используются классы моделей. Hotel ссылается на Address, сложный одноуровневый тип (многокомпонентное поле), и Room (коллекцию многокомпонентных полей).

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

// Use-case: <Hotel> in a field definition
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));

// Use-case: <Hotel> in a response
private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.GetResults())
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

Альтернативный подход — добавление полей непосредственно в индекс. В следующем примере показано лишь несколько полей.

 SearchIndex index = new SearchIndex(indexName)
 {
     Fields =
         {
             new SimpleField("hotelId", SearchFieldDataType.String) { IsKey = true, IsFilterable = true, IsSortable = true },
             new SearchableField("hotelName") { IsFilterable = true, IsSortable = true },
             new SearchableField("hotelCategory") { IsFilterable = true, IsSortable = true },
             new SimpleField("baseRate", SearchFieldDataType.Int32) { IsFilterable = true, IsSortable = true },
             new SimpleField("lastRenovationDate", SearchFieldDataType.DateTimeOffset) { IsFilterable = true, IsSortable = true }
         }
 };

Определения полей

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

В каждом классе поле задается с помощью типа данных и атрибутов, определяющих его использование. Имя каждого общедоступного свойства в каждом классе сопоставляется с полем с тем же именем в определении индекса.

Рассмотрим следующий фрагмент кода, который извлекает несколько определений полей из класса Hotel. Address и Rooms являются типами C# с собственными определениями классов (см. пример кода, если вы хотите просмотреть их). Оба эти типа являются сложными. Подробнее см. в статье Как моделировать сложные типы.

public partial class Hotel
{
    [SimpleField(IsKey = true, IsFilterable = true)]
    public string HotelId { get; set; }

    [SearchableField(IsSortable = true)]
    public string HotelName { get; set; }

    [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
    public string Description { get; set; }

    [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
    public string Category { get; set; }

    [JsonIgnore]
    public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

    [SearchableField]
    public Address Address { get; set; }

    public Room[] Rooms { get; set; }

Выбор класса поля

При определении полей можно использовать базовый SearchField класс или использовать производные вспомогательные модели, которые служат шаблонами с предварительно настроенными свойствами.

Ключом документа (IsKey = true) должно служить только одно поле в индексе. Оно должно быть строкой и должно уникальным образом идентифицировать каждый документ. Также требуется наличие IsHidden = true, что означает, что этот элемент не может отображаться в результатах поиска.

Тип поля Описание и использование
SearchField Базовый класс, для большинства свойств которого задано значение NULL, за исключением Name обязательного значения, и AnalyzerName который по умолчанию используется для стандартной Lucene.
SimpleField Вспомогательная модель. Может относиться к любому типу данных, всегда недоступна для поиска (игнорируется в запросах полнотекстового поиска) и ее можно извлечь (она не скрыта). Другие атрибуты отключены по умолчанию, но их можно включить. SimpleField можно использовать для идентификаторов документов или полей, используемых только в фильтрах, аспектах или профилях оценки. Если это так, обязательно примените все необходимые для сценария атрибуты, например IsKey = true для идентификатора документа. Чтобы узнать больше, см. SimpleFieldAttribute.cs в исходном коде.
SearchableField Вспомогательная модель. Должна быть строкой и всегда доступна для поиска и извлечения. Другие атрибуты отключены по умолчанию, но их можно включить. Так как этот тип поля доступен для поиска, он поддерживает синонимы и полное дополнение свойств анализатора. Чтобы узнать больше, см. SearchableFieldAttribute.cs в исходном коде.

Независимо от того, используется ли базовый API SearchField или одна из вспомогательных моделей, необходимо явно включить атрибуты фильтров, аспектов и сортировки. Например, IsFilterable, IsSortable и IsFacetable должны быть явно атрибутированы, как показано в предыдущем примере.

Добавление атрибутов поля

Обратите внимание, что каждое поле обладает атрибутами, такими как IsFilterable, IsSortable, IsKey и AnalyzerName. Эти атрибуты напрямую сопоставляются с соответствующими атрибутами поля в индексе Поиск с использованием ИИ Azure. Класс FieldBuilder на основе этих свойств создает определения полей для индекса.

Сопоставление типов полей

Типы .NET свойств сопоставляются с эквивалентными типами полей в определении индекса. Например, свойство строки Category сопоставляется с полем category, которое имеет тип Edm.String. Аналогичные сопоставления присутствуют между типами bool?, Edm.Boolean, DateTimeOffset?, Edm.DateTimeOffset и т. д.

Вы заметили свойство SmokingAllowed?

[JsonIgnore]
public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

Атрибут JsonIgnore в этом свойстве указывает для FieldBuilder, что его не следует сериализовать в индекс в качестве поля. Это отличный способ создать клиентские вычисляемые свойства, которые можно использовать в качестве вспомогательных в приложении. В этом случае свойство SmokingAllowed указывает, допускает ли курение хотя бы один объект Room в коллекции Rooms. Если все являются ложными, это означает, что весь отель не разрешает курить.

Загрузка индекса.

Следующий шаг в Main заполняет только что созданный индекс hotels. Заполнение индекса выполняется следующим методом: (Часть кода заменена на ... в целях иллюстрации. Полный код заполнения данных см. в полном примере решения.)

private static void UploadDocuments(SearchClient searchClient)
{
    IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "1",
                HotelName = "Stay-Kay City Hotel",
                ...
                Address = new Address()
                {
                    StreetAddress = "677 5th Ave",
                    ...
                },
                Rooms = new Room[]
                {
                    new Room()
                    {
                        Description = "Budget Room, 1 Queen Bed (Cityside)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Budget Room, 1 King Bed (Mountain View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Deluxe Room, 2 Double Beds (City View)",
                        ...
                    }
                }
            }),
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "2",
                HotelName = "Old Century Hotel",
                ...
                {
                    StreetAddress = "140 University Town Center Dr",
                    ...
                },
                Rooms = new Room[]
                {
                    new Room()
                    {
                        Description = "Suite, 2 Double Beds (Mountain View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Standard Room, 1 Queen Bed (City View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Budget Room, 1 King Bed (Waterfront View)",
                        ...
                    }
                }
            }),
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "3",
                HotelName = "Gastronomic Landscape Hotel",
                ...
                Address = new Address()
                {
                    StreetAddress = "3393 Peachtree Rd",
                    ...
                },
                Rooms = new Room[]
                {
                    new Room()
                    {
                        Description = "Standard Room, 2 Queen Beds (Amenities)",
                        ...
                    },
                    new Room ()
                    {
                        Description = "Standard Room, 2 Double Beds (Waterfront View)",
                        ...
                    },
                    new Room()
                    {
                        Description = "Deluxe Room, 2 Double Beds (Cityside)",
                        ...
                    }
                }
            }
        };

    try
    {
        IndexDocumentsResult result = searchClient.IndexDocuments(batch);
    }
    catch (Exception)
    {
        // Sometimes when your Search service is under load, indexing will fail for some of the documents in
        // the batch. Depending on your application, you can take compensating actions like delaying and
        // retrying. For this simple demo, we just log the failed document keys and continue.
        Console.WriteLine("Failed to index some of the documents: {0}");
    }

    Console.WriteLine("Waiting for documents to be indexed...\n");
    Thread.Sleep(2000);

Этот метод состоит из четырех частей. Первый создает массив из трех Hotel объектов с тремя Room объектами, которые служат входными данными для отправки в индекс. Эти данные жестко запрограммированы для простоты. В фактическом приложении данные, скорее всего, приходят из внешнего источника данных, например базы данных SQL.

Вторая часть создает IndexDocumentsBatch, содержащий документы. Вы указываете операцию, которую необходимо применить к пакету во время его создания, в данном случае путем вызова метода IndexDocumentsAction.Upload. Затем пакет отправляется в индекс поиска ИИ Azure с помощью IndexDocuments метода.

Примечание.

В этом примере вы просто отправляете документы. Если требуется внести изменения в существующие документы или удалить документы, можно создать пакеты, вызвав методы IndexDocumentsAction.Merge, IndexDocumentsAction.MergeOrUpload или IndexDocumentsAction.Delete. Вы также можете смешивать различные операции в одном пакете, вызвав IndexBatch.Newколлекцию IndexDocumentsAction объектов, каждый из которых сообщает службе "Поиск ИИ Azure" выполнить определенную операцию в документе. Можно создать каждый IndexDocumentsAction с отдельной операцией, вызвав соответствующий метод, например IndexDocumentsAction.Merge, IndexAction.Upload и т. д.

Третья часть метода — это блок catch, который обрабатывает важные ошибки индексирования. Если службе поиска не удается проиндексировать некоторые документы в пакете, возникает исключение RequestFailedException. Исключение может произойти, если вы индексируете документы, пока служба находится под большой нагрузкой. Настоятельно рекомендуется явно обрабатывать этот случай в коде. Вы можете задержать и повторить попытку индексирования соответствующих документов либо занести ошибку в журнал и продолжить работу, как в нашем примере, а также выполнить другие действия в зависимости от требований вашего приложения к целостности данных. Альтернативой является использование SearchIndexingBufferedSender для интеллектуальной пакетной обработки, автоматической очистки и повторных попыток для неудачных действий индексирования. Дополнительные сведения см. в примере SearchIndexingBufferedSender.

И наконец, метод UploadDocuments вызывает задержку на две секунды. Индексирование в службе поиска происходит асинхронно, поэтому образец приложения должен подождать немного, пока документы не станут доступными для поиска. Такие задержки обычно необходимы только в демонстрациях, тестах и примерах приложений.

Вызовите UploadDocuments в Main()

Следующий фрагмент кода настраивает экземпляр SearchClient с помощью метода GetSearchClient в indexClient. indexClient использует для своих запросов ключ API администратора, который необходим для загрузки или обновления документов.

Альтернативный подход — прямой вызов SearchClient с передачей ключа API администратора в AzureKeyCredential.

SearchClient searchClient = indexClient.GetSearchClient(indexName);

Console.WriteLine("{0}", "Uploading documents...\n");
UploadDocuments(searchClient);

Выполнение запросов

Сначала настройте SearchClient, который считывает конечную точку сервиса и ключ API для запросов из appsettings.json:

private static SearchClient CreateSearchClientForQueries(string indexName, IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["YourSearchServiceEndPoint"];
    string queryApiKey = configuration["YourSearchServiceQueryApiKey"];

    SearchClient searchClient = new SearchClient(new Uri(searchServiceEndPoint), indexName, new AzureKeyCredential(queryApiKey));
    return searchClient;
}

Затем определите метод, отправляющий запрос.

Каждый раз, когда метод выполняет запрос, он создает новый объект SearchOptions. Этот объект позволяет задать дополнительные параметры запроса, например сортировку, фильтрацию, разбиение на страницы и фасетизацию. В этом методе мы задаем свойства Filter, Select и OrderBy для разных запросов. Дополнительные сведения о синтаксисе выражений запросов см. в статье Синтаксис простых запросов.

Следующий шаг — выполнение запросов. Выполнение поиска выполняется с помощью метода SearchClient.Search. Для каждого запроса передайте текст поиска в качестве строки (или "*" если нет текста поиска), а также параметры поиска, созданные ранее. Мы также указываем Hotel в качестве параметра типа для SearchClient.Search, что указывает пакету SDK десериализовать документы в результатах поиска в объекты типа Hotel.

private static void RunQueries(SearchClient searchClient)
{
    SearchOptions options;
    SearchResults<Hotel> results;

    Console.WriteLine("Query 1: Search for 'motel'. Return only the HotelName in results:\n");

    options = new SearchOptions();
    options.Select.Add("HotelName");

    results = searchClient.Search<Hotel>("motel", options);

    WriteDocuments(results);

    Console.Write("Query 2: Apply a filter to find hotels with rooms cheaper than $100 per night, ");
    Console.WriteLine("returning the HotelId and Description:\n");

    options = new SearchOptions()
    {
        Filter = "Rooms/any(r: r/BaseRate lt 100)"
    };
    options.Select.Add("HotelId");
    options.Select.Add("Description");

    results = searchClient.Search<Hotel>("*", options);

    WriteDocuments(results);

    Console.Write("Query 3: Search the entire index, order by a specific field (lastRenovationDate) ");
    Console.Write("in descending order, take the top two results, and show only hotelName and ");
    Console.WriteLine("lastRenovationDate:\n");

    options =
        new SearchOptions()
        {
            Size = 2
        };
    options.OrderBy.Add("LastRenovationDate desc");
    options.Select.Add("HotelName");
    options.Select.Add("LastRenovationDate");

    results = searchClient.Search<Hotel>("*", options);

    WriteDocuments(results);

    Console.WriteLine("Query 4: Search the HotelName field for the term 'hotel':\n");

    options = new SearchOptions();
    options.SearchFields.Add("HotelName");

    //Adding details to select, because "Location" isn't supported yet when deserializing search result to "Hotel"
    options.Select.Add("HotelId");
    options.Select.Add("HotelName");
    options.Select.Add("Description");
    options.Select.Add("Category");
    options.Select.Add("Tags");
    options.Select.Add("ParkingIncluded");
    options.Select.Add("LastRenovationDate");
    options.Select.Add("Rating");
    options.Select.Add("Address");
    options.Select.Add("Rooms");

    results = searchClient.Search<Hotel>("hotel", options);

    WriteDocuments(results);
}

В-третьих, определите метод, записывающий ответ с выводом каждого документа на консоль:

private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.GetResults())
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

Вызовите RunQueries в Main()

SearchClient indexClientForQueries = CreateSearchClientForQueries(indexName, configuration);

Console.WriteLine("{0}", "Running queries...\n");
RunQueries(indexClientForQueries);

Изучение конструкций запросов

Давайте подробнее рассмотрим каждый из запросов. Ниже приведен код для выполнения первого запроса:

options = new SearchOptions();
options.Select.Add("HotelName");

results = searchClient.Search<Hotel>("motel", options);

WriteDocuments(results);

В этом случае мы ищем весь индекс для слов мотеля в любом поле поиска, и мы хотим получить только имена отелей, как указано в параметре Select . Результаты приведены ниже.

Name: Stay-Kay City Hotel

Name: Old Century Hotel

Во втором запросе используйте фильтр для выбора комнат с ночной ставкой менее $ 100. Запрос возвращает в результатах только идентификатор отеля и описание:

options = new SearchOptions()
{
    Filter = "Rooms/any(r: r/BaseRate lt 100)"
};
options.Select.Add("HotelId");
options.Select.Add("Description");

results = searchClient.Search<Hotel>("*", options);

Этот запрос использует выражение OData $filter, Rooms/any(r: r/BaseRate lt 100), для фильтрации документов в индексе. Он использует оператор any, чтобы применить условие 'BaseRate lt 100' к каждому элементу в коллекции Rooms. Дополнительные сведения см. в статье о синтаксисе фильтров OData.

В третьем запросе нам нужно найти две гостиницы с самым свежим ремонтом и отобразить для них название и дату ремонта. Вот этот код:

options =
    new SearchOptions()
    {
        Size = 2
    };
options.OrderBy.Add("LastRenovationDate desc");
options.Select.Add("HotelName");
options.Select.Add("LastRenovationDate");

results = searchClient.Search<Hotel>("*", options);

WriteDocuments(results);

В последнем запросе найдите все имена отелей, соответствующие слову:

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Description");
options.Select.Add("Category");
options.Select.Add("Tags");
options.Select.Add("ParkingIncluded");
options.Select.Add("LastRenovationDate");
options.Select.Add("Rating");
options.Select.Add("Address");
options.Select.Add("Rooms");

results = searchClient.Search<Hotel>("hotel", options);

WriteDocuments(results);

Этот раздел завершает знакомство с пакетом SDK для .NET, однако не стоит останавливаться. В следующем разделе приведены другие ресурсы для получения дополнительных возможностей по программированию с помощью службы "Поиск ИИ Azure".