Como usar Azure.Search.Documents em um aplicativo .NET em C#

Este artigo explica como criar e gerenciar objetos de pesquisa usando C# e a biblioteca de clientes do Azure.Search.Documents (versão 11) no SDK do Azure para o .NET.

Sobre a versão 11

O SDK do Azure para .NET inclui uma biblioteca de clientes Azure.Search.Documents da equipe do SDK do Azure que é funcionalmente equivalente à biblioteca de clientes anterior, Microsoft.Azure.Search. A versão 11 é mais consistente em termos de programação do Azure. Alguns exemplos incluem a autenticação de chave AzureKeyCredential e System.Text.Json.Serialization para serialização JSON.

Assim como nas versões anteriores, você pode usar essa biblioteca para:

  • Criar e gerenciar índices de pesquisa, fontes de dados, indexadores, conjuntos de habilidades e mapas de sinônimos
  • Carregar e gerenciar documentos de pesquisa em um índice
  • Execute consultas, tudo sem precisar lidar com os detalhes de HTTP e JSON
  • Invocar e gerenciar enriquecimento de IA (conjuntos de habilidades) e saídas

A biblioteca é distribuída como um único pacote Azure.Search.Documents NuGet, que inclui todas as APIs usadas para acesso programático a um serviço de pesquisa.

A biblioteca de cliente define classes como SearchIndex, SearchField e SearchDocument, e operações como SearchIndexClient.CreateIndex e SearchClient.Search nas classes SearchIndexClient e SearchClient. Essas classes são organizadas nos namespaces a seguir:

O Azure.Search.Documents (versão 11) tem como destino a especificação de serviço de pesquisa 2020-06-30.

A biblioteca de clientes não fornece operações de gerenciamento de serviços, como criar e dimensionar serviços de pesquisa e gerenciar chaves de API. Se você precisar gerenciar seus recursos de pesquisa de um aplicativo .NET, use a biblioteca Microsoft.Azure.Management.Search no SDK do Azure para .NET.

Fazer upgrade para v11

Se você estiver usando a versão anterior do SDK do .NET e quiser atualizar para a versão atual disponível, confira Atualizar para o SDK do .NET do Azure AI Search versão 11.

Requisitos do SDK

  • Visual Studio 2019 ou posterior.

  • Seu próprio serviço Azure AI Search. Para usar o SDK, você precisará do nome do serviço e de uma ou mais chaves de API. Crie um serviço no portal se você não tiver um.

  • Baixe o pacote Azure.Search.Documents usando Ferramentas>Gerenciador de pacotes NuGet>Gerenciar pacotes NuGet para solução no Visual Studio. Pesquise o nome do pacote Azure.Search.Documents.

O SDK do Azure para .NET está em conformidade com o .NET Standard 2.0.

Aplicativo de exemplo

Este artigo "ensina por exemplo", contando com o exemplo de código DotNetHowTo no GitHub para ilustrar conceitos fundamentais no Azure AI Search, especificamente, como criar, carregar e consultar um índice de pesquisa.

Para o restante deste artigo, suponha um novo índice chamado "hotéis", preenchido com alguns documentos, com várias consultas que correspondem aos resultados.

Abaixo está o programa principal, mostrando o fluxo geral:

// 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();
}

Em seguida, há uma captura de tela parcial da saída, supondo que você execute esse aplicativo com um nome de serviço válido e chaves de API:

Screenshot of the Console.WriteLine output from the sample program.

Tipos de cliente

A biblioteca de cliente usa três tipos de cliente para várias operações: SearchIndexClient para criar, atualizar ou excluir índices, SearchClient para carregar ou consultar um índice e SearchIndexerClient para trabalhar com indexadores e conjuntos de habilidades. Este artigo se concentra nas duas primeiras.

No mínimo, todos os clientes exigem o nome do serviço ou o ponto de extremidade e uma chave de API. É comum fornecer essas informações em um arquivo de configuração, semelhante ao que você encontra no arquivo appsettings.json do aplicativo de exemplo DotNetHowTo. Para ler o arquivo de configuração, adicione using Microsoft.Extensions.Configuration; ao seu programa.

A instrução a seguir cria o cliente de índice usado para criar, atualizar ou excluir índices. Ele usa um ponto de extremidade de serviço e uma chave de API de administração.

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

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

A próxima instrução cria o cliente de pesquisa usado para carregar documentos ou executar consultas. SearchClient requer um índice. Você precisará de uma chave de API de administração para carregar documentos, mas pode usar uma chave de API de consulta para executar consultas.

string indexName = configuration["SearchIndexName"];

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

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

Observação

Se você fornecer uma chave inválida para a operação de importação (por exemplo, uma chave de consulta quando era necessário fornecer uma chave de administrador), o SearchClient lançará uma CloudException com a mensagem de erro "Proibido" na primeira vez que você chamar um método de operação. Se isso ocorrer, verifique novamente a chave da API.

Excluindo o índice

Nos estágios iniciais do desenvolvimento, talvez você queira incluir uma instrução DeleteIndex para excluir um índice de trabalho em andamento, para que possa recriá-lo com uma definição atualizada. O código de exemplo para o Azure AI Search geralmente inclui uma etapa de exclusão, para que você possa executar novamente o exemplo.

A linha a seguir chama DeleteIndexIfExists:

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

Esse método usa o SearchIndexClient fornecido para verificar se o índice existe e, nesse caso, o exclui:

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.");

Observação

O código de exemplo neste artigo usa os métodos síncronos para simplificar, mas você deve usar os métodos assíncronos em seus próprios aplicativos para mantê-los escalonáveis e responsivos. Por exemplo, no método acima você pode usar DeleteIndexAsync em vez de DeleteIndex.

Crie um índice

Você pode usar SearchIndexClient para criar um índice.

O método abaixo cria um novo objeto SearchIndex com uma lista de objetos SearchField que definem o esquema do novo índice. Cada campo tem um nome, tipo de dados e vários atributos que definem seu comportamento de pesquisa.

Os campos podem ser definidos de uma classe de modelo usando FieldBuilder. A classe FieldBuilder usa a reflexão para criar uma lista de objetos SearchField para o índice, examinando os atributos e propriedades públicas da classe do modelo Hotel determinada. Posteriormente, faremos uma análise mais detalhada da classe 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);
}

Além dos campos, você também pode adicionar perfis de pontuação, sugestões ou opções de CORS para o índice (esses parâmetros foram omitidos do exemplo por questão de brevidade). Você pode saber mais sobre o objeto SearchIndex e suas partes na lista de propriedades SearchIndex, bem como na Referência da API REST.

Observação

Você pode criar quando desejar a lista de objetos Field diretamente, em vez de usar o FieldBuilder se necessário. Por exemplo, você pode não querer usar uma classe de modelo ou pode precisar usar uma classe de modelo existente que não deseja modificar adicionando atributos.

Chamar CreateIndex em Main()

Main cria um novo índice "hotéis" chamando o método acima:

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

Usar uma classe de modelo para representação de dados

O exemplo DotNetHowTo usa classes de modelo para as estruturas de dados de Hotel, Endereçoe Quarto. Hotel faz referência a Address, um tipo complexo de nível único (um campo de várias partes), e Room (uma coleção de campos de várias partes).

Você pode usar esses tipos para criar e carregar o índice e para estruturar a resposta de uma consulta:

// 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();
}

Uma abordagem alternativa é adicionar campos a um índice diretamente. O exemplo a seguir mostra apenas alguns campos.

 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 }
         }
 };

Definições do campo

Seu modelo de dados no .NET e seu esquema de índice correspondente devem dar suporte à experiência de pesquisa que você gostaria de dar ao usuário final. Cada objeto de nível superior no .NET, como um documento de pesquisa em um índice de pesquisa, corresponde a um resultado de pesquisa que você apresentaria na sua interface do usuário. Por exemplo, em um aplicativo de pesquisa de hotel, seus usuários finais talvez queiram pesquisar por nome de hotel, recursos do hotel ou as características de um quarto específico.

Dentro de cada classe, um campo é definido com um tipo de dados e atributos que determinam como ele é usado. O nome de cada propriedade pública em cada classe é mapeado para um campo com o mesmo nome na definição de índice.

Examine o trecho a seguir que efetua pull de várias definições de campo da classe Hotel. Observe que o endereço e os quartos são tipos C# com suas próprias definições de classe (consulte o código de exemplo se você quiser exibi-los). Ambos são tipos complexos. Para obter mais informações, consulteComo modelar os tipos complexos.

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; }

Escolhendo uma classe de campo

Ao definir campos, você pode usar a classe SearchField de base ou usar modelos auxiliares derivados que servem como "modelos", com propriedades pré-configuradas.

Exatamente um campo no índice deve servir como a chave do documento (IsKey = true). Ele deve ser uma cadeia de caracteres e identificar exclusivamente cada documento. Também é necessário ter IsHidden = true, o que significa que não pode ser visível nos resultados da pesquisa.

Tipo de Campo Descrição e uso
SearchField Classe de base, com a maioria das propriedades definidas como null, exceto Name, que é necessária, e AnalyzerName, que usa como padrão o Lucene padrão.
SimpleField Modelo auxiliar. Pode ser qualquer tipo de dados, sempre é não pesquisável (ignorado em consultas de pesquisa de texto completo) e é recuperável (não está oculto). Os outros atributos estão desativados por padrão, mas podem ser habilitados. Você pode usar um SimpleField para IDs ou campos de documentos usados somente em filtros, facetas ou perfis de pontuação. Nesse caso, lembre-se de aplicar todos os atributos necessários para o cenário, como IsKey = true para uma ID de documento. Para obter mais informações, confira SimpleFieldAttribute.cs no código-fonte.
SearchableField Modelo auxiliar. Precisa ser uma cadeia de caracteres e sempre é pesquisável e recuperável. Os outros atributos estão desativados por padrão, mas podem ser habilitados. Como esse tipo de campo é pesquisável, ele dá suporte a sinônimos e ao complemento completo de propriedades do analisador. Para obter mais informações, confira SearchableFieldAttribute.cs no código-fonte.

Se você usar a API SearchField básica ou um dos modelos auxiliares, precisará habilitar explicitamente os atributos de filtro, faceta e classificação. Por exemplo, IsFilterable, IsSortable e IsFacetable devem ser explicitamente atribuídos, conforme mostrado no exemplo acima.

Adicionando atributos de campo

Observe como cada campo é decorado com atributos como IsFilterable, IsSortable, IsKey e AnalyzerName. Esses atributos são mapeados diretamente para os atributos de campo correspondentes em um índice do Azure AI Search. A classe FieldBuilder as utiliza para criar definições de campo para o índice.

Mapeamento de tipo de campo

Os tipos .NET dessas propriedades são mapeados para seus tipos de campo equivalentes na definição do índice. Por exemplo, a propriedade de cadeia de caracteres Category mapeia para o campo category, que é do tipo Edm.String. Há mapeamentos de tipo semelhantes entre bool?, Edm.Boolean, DateTimeOffset? e Edm.DateTimeOffset etc.

Você observou a propriedade SmokingAllowed?

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

O atributo JsonIgnore nessa propriedade informa ao FieldBuilder para não serializá-lo ao índice como um campo. Essa é uma ótima maneira de criar propriedades calculadas no lado do cliente que podem ser usadas como auxiliares no aplicativo. Nesse caso, a propriedade SmokingAllowed indica se algum Room na coleção Rooms permite fumar. Quando a resposta é false para todos, isso indica que não é permitido fumar em nenhuma parte do hotel.

Carregue um índice

A próxima etapa em Main popula o índice "hotéis" criado recentemente. Essa população de índice é feita no seguinte método: (algum código substituído por "..." para fins de ilustração. Confira a solução de amostra completa para o código de população de dados completo.)

private static void UploadDocuments(SearchClient searchClient)
{
    IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
        IndexDocumentsAction.Upload(
            new Hotel()
            {
                HotelId = "1",
                HotelName = "Secret Point Motel",
                ...
                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 = "Twin Dome Motel",
                ...
                {
                    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 = "Triple 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);

Este método tem quatro partes. A primeira cria uma matriz de três objetos Hotel, cada um com três objetosRoom que atuarão como nossos dados de entrada para o carregamento no índice. Esses dados são codificados para manter a simplicidade. Em um aplicativo real, os dados provavelmente virão de uma fonte de dados externa, como um banco de dados SQL.

A segunda parte cria um IndexDocumentsBatch com os documentos. Especifique a operação que você quer aplicar ao lote no momento da criação dele, nesse caso, chamando IndexDocumentsAction.Upload. O lote é então carregado para o índice do Azure AI Search pelo método IndexDocuments.

Observação

Neste exemplo, estamos carregando apenas documentos. Se você quiser mesclar alterações em documentos existentes ou excluir documentos, poderá criar lotes chamando IndexDocumentsAction.Merge, IndexDocumentsAction.MergeOrUpload ou IndexDocumentsAction.Delete. Também é possível combinar diferentes operações em um único lote chamando IndexBatch.New, que usa uma coleção de objetos IndexDocumentsAction, com cada um deles informando ao Azure AI Search para executar uma operação específica em um documento. Você pode criar cada IndexDocumentsAction com sua própria operação chamando o método correspondente como IndexDocumentsAction.Merge, IndexAction.Upload e assim por diante.

A terceira parte desse método é um bloco catch que trata um caso de erro importante para indexação. Se o serviço de pesquisa não indexar alguns documentos no lote, uma RequestFailedException será lançada. Uma exceção pode acontecer ao indexar documentos enquanto o serviço está gerando muita carga. É altamente recomendável a manipulação explícita desse caso em seu código. Você pode atrasar e repetir a indexação de documentos que falharam, ou você pode registrar em log e continuar, como faz o exemplo, ou pode alguma outra coisa, dependendo dos requisitos de consistência de dados do aplicativo. Uma alternativa é usar SearchIndexingBufferedSender para envio em lote inteligente, liberação automática e novas tentativas de ações de indexação que falharam. Confira este exemplo para obter mais contexto.

Por fim, o método UploadDocuments atrasa por dois segundos. A indexação ocorre de maneira assíncrona no serviço de pesquisa, portanto, o exemplo de aplicativo precisa aguardar alguns instantes para garantir que os documentos estejam disponíveis para pesquisa. Normalmente, atrasos como esses só são necessários em demonstrações, testes e exemplos de aplicativos.

Chamar UploadDocuments em Main()

O trecho de código a seguir configura uma instância de SearchClient usando o método GetSearchClient de indexClient. O indexClient usa uma chave de API de administração em suas solicitações, que é necessária para carregar ou atualizar documentos.

Uma abordagem alternativa é chamar SearchClient diretamente, passando uma chave de API de administração em AzureKeyCredential.

SearchClient searchClient = indexClient.GetSearchClient(indexName);

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

Executar consultas

Primeiro, configure um SearchClient que leia o ponto de extremidade de serviço e a chave de API de consulta de appsettings.jsem:

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

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

Em segundo lugar, defina um método que envia uma solicitação de consulta.

Cada vez que o método executa uma consulta, ele cria um novo objeto SearchOptions. Esse objeto é usado para especificar opções adicionais para a consulta, como classificação, filtragem, paginação e organização. Nesse método, estamos definindo a propriedade de Filter, Select e OrderBy para consultas diferentes. Para obter mais informações sobre a sintaxe de expressão de consulta de pesquisa, Sintaxe de consulta simples.

A próxima etapa é a execução da consulta. A execução da pesquisa é feita usando o método SearchClient.Search. Para cada consulta, passe o texto de pesquisa a ser usado como uma cadeia de caracteres (ou "*" se não houver um texto de pesquisa) e também as opções de pesquisa criadas anteriormente. Também podemos especificar Hotel como o parâmetro de tipo para SearchClient.Search, que informa ao SDK para desserializar documentos nos resultados da pesquisa em objetos do tipo 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);
}

Em terceiro lugar, defina um método que grave a resposta, imprimindo cada documento no console:

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

    Console.WriteLine();
}

Chamar RunQueries em Main()

SearchClient indexClientForQueries = CreateSearchClientForQueries(indexName, configuration);

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

Explorar constructos de consulta

Vejamos cada uma das consultas com mais detalhes. Este é o código para executar a primeira consulta:

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

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

WriteDocuments(results);

Nesse caso, estamos pesquisando o índice inteiro para a palavra "motel" em qualquer campo pesquisável e só queremos recuperar os nomes dos hotéis, conforme especificado pela opção Select. Estes são os resultados:

Name: Secret Point Motel

Name: Twin Dome Motel

Na segunda consulta, use um filtro para selecionar quartos com uma taxa noturna inferior a US$ 100. Retorne apenas a ID e a descrição do hotel nos resultados:

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

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

A consulta acima usa uma expressão $filter do OData, Rooms/any(r: r/BaseRate lt 100), para filtrar os documentos no índice. Também usa qualquer operador para aplicar 'BaseRate lt 100' a todos os itens na coleção Quartos. Para obter mais informações, consulte Sintaxe de filtro OData.

Na terceira consulta, queremos localizar os dois principais hotéis reformados mais recentemente e mostrar o nome dos hotéis e a data da última reforma. Este é o código :

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);

Na última consulta, localize todos os nomes de hotéis que correspondam à palavra "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);

Esta seção conclui esta introdução ao SDK do .NET, mas não pare aqui. A próxima seção sugere outros recursos para aprender mais sobre programação com o Azure AI Search.

Próximas etapas