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

Observação

Pesquisa de IA do Azure  está disponível por meio do portal Azure, APIs REST e SDKs do Azure. Ele também sustenta o IQ do Foundry, a camada de conhecimento gerenciado que transforma o conteúdo da empresa em bases de conhecimento reutilizáveis e com reconhecimento de permissão para agentes no portal do Microsoft Foundry.

Este artigo explica como criar e gerenciar objetos de pesquisa usando C# e a biblioteca de cliente Azure.Search.Documents do SDK do Azure para .NET.

Você pode usar essa biblioteca para operações de plano de dados, incluindo:

  • 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 o enriquecimento por IA (conjuntos de competências) e saídas

A biblioteca é distribuída como um único pacote 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:

A biblioteca de cliente Azure.Search.Documents 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 a partir de um aplicativo .NET, use a biblioteca Azure.ResourceManager.Search do SDK do Azure para .NET.

Requisitos do SDK

  • Visual Studio 2019 ou posterior.

  • Pesquisa de IA do Azure 

  • Baixe o pacote NuGet 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 na Pesquisa de IA do Azure e como criar, carregar e consultar um índice de pesquisa.

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

O exemplo a seguir mostra o programa principal, com 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:

Captura de tela da saída de Console.WriteLine do programa de exemplo.

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["YourSearchServiceEndPoint"];
    string adminApiKey = configuration["YourSearchServiceAdminApiKey"];

    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ê precisa 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["YourSearchServiceEndPoint"];
    string queryApiKey = configuration["YourSearchServiceQueryApiKey"];

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

Observação

Caso forneça 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ça 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.

Excluir 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 Pesquisa de IA do Azure  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 anterior, você poderia usar DeleteIndexAsync em vez de DeleteIndex.

Crie um índice

Você pode usar SearchIndexClient para criar um índice.

O método a seguir 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, talvez você não queira usar uma classe de modelo ou talvez precise 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 anterior:

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 hotéis, seus usuários finais podem querer pesquisar por nome de hotel, recursos do hotel ou 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.

Dê uma olhada no trecho de código a seguir, que obtém várias definições de campo da classe Hotel. Address e Rooms 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; }

Escolher 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 de documento ou campos usados apenas 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 na amostra anterior.

Adicionar 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 Pesquisa de IA do Azure . 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 string 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 preenche o índice hotéis criado recentemente. Esse preenchimento de índice é feito 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 preenchimento de dados completo.)

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

Este método tem quatro partes. A primeira cria uma matriz de três objetos Hotel, cada um com três objetosRoom que atuam 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 vêm 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 Pesquisa de IA do Azure  pelo método IndexDocuments.

Observação

Neste exemplo, você está 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 Pesquisa de IA do Azure  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 lida com um caso de erro importante relacionado à 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. Recomendamos enfaticamente que você trate esse caso explicitamente em seu código. Você pode adiar e tentar indexar novamente os documentos que falharam, ou registrar em log o erro e continuar, como no exemplo, ou fazer outra coisa, dependendo dos requisitos de consistência de dados da sua aplicação. Uma alternativa é usar SearchIndexingBufferedSender para envio em lote inteligente, liberação automática e novas tentativas de ações de indexação que falharam. Para obter mais contexto, consulte o exemplo SearchIndexingBufferedSender.

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

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 especificamos Hotel como o parâmetro de tipo para SearchClient.Search, o que instrui o SDK a desserializar documentos dos 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: Stay-Kay City Hotel

Name: Old Century Hotel

Na segunda consulta, use um filtro para selecionar quartos com uma taxa de pernoite 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);

Esta consulta usa uma expressão $filter do OData, Rooms/any(r: r/BaseRate lt 100), para filtrar os documentos no índice. Ele usa o operador any para aplicar 'BaseRate lt 100' a cada item na coleção Rooms. 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 Pesquisa de IA do Azure .