Compartilhar via


Como usar o Microsoft.Azure.Search em um aplicativo .NET em C#

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

A versão 10 é a mais recente do pacote Microsoft.Azure.Search. No futuro, novos recursos serão lançados em Azure.Search.Documents pela equipe de SDK do Azure.

Observação

Se você tiver projetos de desenvolvimento existentes ou em andamento, continue usando a versão 10. Para novos projetos ou para usar novos recursos, faça a transição para a nova biblioteca.

Sobre a versão 10

O SDK consiste em algumas bibliotecas de clientes que permitem que você gerencie seus índices, fontes de dados, indexadores e mapas de sinônimos, carregue e gerencie documentos e execute consultas, sem a necessidade de lidar com os detalhes de HTTP e JSON. Essas bibliotecas de cliente são distribuídas como pacotes NuGet.

O principal pacote NuGet é Microsoft.Azure.Search, o que é um pacote meta que inclui todos os outros pacotes, como dependências. Use esse pacote se você estiver começando ou se souber que seu aplicativo precisará de todos os recursos do Azure Cognitive Search.

Os outros pacotes NuGet no SDK são:

  • Microsoft.Azure.Search.Data: use este pacote se você estiver desenvolvendo um aplicativo .NET com o Azure Cognitive Search e só precisar consultar ou atualizar documentos nos índices. Se você também precisa criar ou atualizar índices, mapas de sinônimo ou outros recursos de nível de serviço, use o Microsoft.Azure.Search pacote em vez disso.
  • Microsoft.Azure.Search.Service: use este pacote se você estiver desenvolvendo a automação no .NET para gerenciar índices, mapas de sinônimos, indexadores, fontes de dados ou outros recursos do Azure Cognitive Search no nível do serviço. Se você precisar apenas consultar ou atualizar em seus índices, use o Microsoft.Azure.Search.Data pacote em vez disso. Se você precisar de toda a funcionalidade do Azure Cognitive Search, use o pacote Microsoft.Azure.Search como alternativa.
  • Microsoft.Azure.Search.Common: tipos comuns necessários para as bibliotecas .NET do Azure Cognitive Search. Não é necessário usar esse pacote diretamente no aplicativo. Ele deve ser usado apenas como uma dependência.

A diversas bibliotecas de clientes definem classes como Index, Field e Document, e operações como Indexes.Create e Documents.Search nas classes SearchServiceClient e SearchIndexClient. Essas classes são organizadas nos namespaces a seguir:

Para fornecer comentários sobre uma atualização futura do SDK, consulte a página de comentários ou crie um problema no GitHub e mencione "Azure Cognitive Search" no título.

O SDK do .NET dá suporte à versão 2019-05-06 da API REST do Azure Cognitive Search. Essa versão inclui o suporte para tipos complexos, enriquecimento de IA, preenchimento automático e modo de análise JsonLines ao indexar blobs do Azure.

Esse SDK não oferece suporte a Operações de Gerenciamento, como criar e dimensionar os serviços do Search e gerenciar chaves de API. Para gerenciar seus recursos do Search em um aplicativo .NET, use o SDK de gerenciamento .NET do Azure Cognitive Search.

Atualizar para a v10

Consulte este artigo para saber como atualizar uma versão mais antiga do SDK .NET do Azure Cognitive Search para a mais recente que oferece disponibilidade geral.

Requisitos do SDK

  1. Visual Studio 2017 ou mais recente.
  2. Seu próprio serviço Azure Cognitive Search. Para usar o SDK, você precisará do nome do serviço e de uma ou mais chaves de API. Criar um serviço no portal ajudará você com estas etapas.
  3. Baixe o pacote NuGet do SDK .NET do Azure Cognitive Search usando "Gerenciar pacotes NuGet" no Visual Studio. Basta procurar o nome do pacote Microsoft.Azure.Search em NuGet.org (ou um dos outros nomes de pacote acima se você precisar somente de um subconjunto da funcionalidade).

O SDK .NET do Azure Cognitive Search oferece suporte a aplicativos voltados ao .NET Framework 4.5.2 e mais recente e ao .NET Core 2.0 e mais recente.

Principais cenários

Há várias coisas que você precisará fazer em seu aplicativo de pesquisa. Neste tutorial, abordaremos os principais cenários:

  • Criando um índice
  • Preenchimento do índice com documentos
  • Procura por documentos usando filtros e pesquisa de texto completo

O seguinte código de amostra ilustra cada um desses cenários. Fique à vontade para usar os snippets de código em seu próprio aplicativo.

Visão geral

O exemplo de aplicativo que vamos explorar cria um novo índice chamado "hotéis", preenche-o com alguns documentos e, em seguida, executa algumas consultas de pesquisa. Este é 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();

    SearchServiceClient serviceClient = CreateSearchServiceClient(configuration);

    string indexName = configuration["SearchIndexName"];

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

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

    ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName);

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

    ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(configuration);

    RunQueries(indexClientForQueries);

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

Observação

Você pode encontrar o código-fonte completo do aplicativo de exemplo usado neste passo a passo no GitHub.

Vamos examinar isso passo a passo. Primeiro, é necessário criar um novo SearchServiceClient. Esse objeto permite o gerenciamento de índices. Para criar um, forneça o nome do serviço Azure Cognitive Search, bem como uma chave de API de administração. Você pode inserir essas informações no arquivo appsettings.json do aplicativo de exemplo.

private static SearchServiceClient CreateSearchServiceClient(IConfigurationRoot configuration)
{
    string searchServiceName = configuration["SearchServiceName"];
    string adminApiKey = configuration["SearchServiceAdminApiKey"];

    SearchServiceClient serviceClient = new SearchServiceClient(searchServiceName, new SearchCredentials(adminApiKey));
    return serviceClient;
}

Observação

Se você fornecer uma chave incorreta (por exemplo, uma chave de consulta quando era necessário fornecer uma chave de administrador), o SearchServiceClient lançará uma CloudException com a mensagem de erro "Forbidden" na primeira vez que você chamar um método de operação, como Indexes.Create. Se isso ocorrer, verifique novamente a chave da API.

As próximas linhas chamam métodos para criação de um índice chamado "hotéis," excluindo-o primeiro caso ele já exista. Abordaremos esses métodos um pouco mais tarde.

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

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

Em seguida, o índice precisa ser preenchido. Para preencher o índice, será necessário um SearchIndexClient. Há duas maneiras de obter um: construindo ou chamando Indexes.GetClient no SearchServiceClient. Usaremos o último por conveniência.

ISearchIndexClient indexClient = serviceClient.Indexes.GetClient(indexName);

Observação

Em um aplicativo de pesquisa comum, o gerenciamento e o preenchimento do índice são feitos por um componente distinto das consultas de pesquisa. Indexes.GetClient é conveniente para preencher um índice porque evita o trabalho de fornecer SearchCredentials adicional. Ele faz isso passando a chave de administrador que você usou para criar o SearchServiceClient ao novo SearchIndexClient. No entanto, na parte do aplicativo que executa consultas, é melhor criar diretamente o SearchIndexClient para transmitir uma chave de consulta em vez de uma de administração. Isso está de acordo com o princípio de privilégios mínimos e ajuda a tornar seu aplicativo mais seguro. Você pode saber mais sobre chaves de administração e chaves de consulta aqui.

Agora que temos um SearchIndexClient, podemos preencher o índice. O preenchimento do índice é feito por outro método que será abordado posteriormente.

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

Por fim, executamos algumas consultas de pesquisa e exibimos os resultados. Dessa vez, usamos outro SearchIndexClient:

ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(indexName, configuration);

RunQueries(indexClientForQueries);

Posteriormente, faremos uma análise mais detalhada do método RunQueries. Este é o código para criar o novo SearchIndexClient:

private static SearchIndexClient CreateSearchIndexClient(string indexName, IConfigurationRoot configuration)
{
    string searchServiceName = configuration["SearchServiceName"];
    string queryApiKey = configuration["SearchServiceQueryApiKey"];

    SearchIndexClient indexClient = new SearchIndexClient(searchServiceName, indexName, new SearchCredentials(queryApiKey));
    return indexClient;
}

Dessa vez, usaremos uma chave de consulta, já que não precisamos de acesso de gravação para o índice. Você pode inserir essas informações no arquivo appsettings.json do aplicativo de exemplo.

Ao executar esse aplicativo com um nome de serviço válido e chaves de API, a saída deve ser semelhante a este exemplo: (algumas saídas de console foram substituídas por "..." para fins de ilustração).


Deleting index...

Creating index...

Uploading documents...

Waiting for documents to be indexed...

Search the entire index for the term 'motel' and return only the HotelName field:

Name: Secret Point Motel

Name: Twin Dome Motel


Apply a filter to the index to find hotels with a room cheaper than $100 per night, and return the hotelId and description:

HotelId: 1
Description: The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Times Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.

HotelId: 2
Description: The hotel is situated in a  nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.


Search the entire index, order by a specific field (lastRenovationDate) in descending order, take the top two results, and show only hotelName and lastRenovationDate:

Name: Triple Landscape Hotel
Last renovated on: 9/20/2015 12:00:00 AM +00:00

Name: Twin Dome Motel
Last renovated on: 2/18/1979 12:00:00 AM +00:00


Search the hotel names for the term 'hotel':

HotelId: 3
Name: Triple Landscape Hotel
...

Complete.  Press any key to end application... 

O código-fonte completo do aplicativo é fornecido ao final deste artigo.

Em seguida, analisaremos com mais detalhes cada um dos métodos chamados pelo Main.

Criando um índice

Depois de criar um SearchServiceClient, Main exclua o índice "hotéis", caso ele já exista. Essa exclusão é feita pelo método a seguir:

private static void DeleteIndexIfExists(string indexName, SearchServiceClient serviceClient)
{
    if (serviceClient.Indexes.Exists(indexName))
    {
        serviceClient.Indexes.Delete(indexName);
    }
}

Esse método usa o SearchServiceClient fornecido para verificar se o índice existe e, nesse caso, excluí-lo.

Observação

O código de exemplo deste artigo usa os métodos síncronos do SDK .NET do Azure Cognitive Search para manter a simplicidade. Recomendamos que você use 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 ExistsAsync e DeleteAsync em vez de Exists e Delete.

Em seguida, Main cria um novo índice "hotéis" chamando este método:

private static void CreateIndex(string indexName, SearchServiceClient serviceClient)
{
    var definition = new Index()
    {
        Name = indexName,
        Fields = FieldBuilder.BuildForType<Hotel>()
    };
    
    serviceClient.Indexes.Create(definition);
}

Esse método cria um novo objeto Index com uma lista de objetos Field 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. A classe FieldBuilder usa a reflexão para criar uma lista de objetos Field 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.

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.

Além dos campos, você também pode adicionar perfis de pontuação, sugestores ou opções CORS ao índice (esses parâmetros foram omitidos do exemplo por questão de brevidade). Saiba mais sobre o objeto do índice e suas partes na Referência de SDK, bem como na Referência da API REST do Azure Cognitive Search.

Preenchendo o índice

A próxima etapa em Main preenche o índice recém-criado. 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(ISearchIndexClient indexClient)
{
    var hotels = new Hotel[]
    {
        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)",
                    ...
                }
            }
        },
        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)",
                    ...
                }
            }
        },
        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)",
                    ...
                }
            }
        }
    };

    var batch = IndexBatch.Upload(hotels);

    try
    {
        indexClient.Documents.Index(batch);
    }
    catch (IndexBatchException e)
    {
        // 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}",
            String.Join(", ", e.IndexingResults.Where(r => !r.Succeeded).Select(r => r.Key)));
    }

    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 Room objetos que atuarão como nossos dados de entrada para o carregamento no índice. Esses dados são codificados para manter a simplicidade. Em seu próprio aplicativo, provavelmente seus dados virão de uma fonte de dados externa, como um banco de dados SQL.

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

Observação

Neste exemplo, estamos carregando apenas documentos. Se você quiser mesclar alterações em documentos existentes ou excluir documentos, poderá criar lotes chamando IndexBatch.Merge, IndexBatch.MergeOrUpload ou IndexBatch.Delete. Também é possível combinar diferentes operações em um único lote chamando IndexBatch.New, que usa uma coleção de objetos IndexAction, cada um informando ao Azure Cognitive Search para executar uma determinada operação em um documento. Você pode criar cada IndexAction com sua própria operação chamando o método correspondente como IndexAction.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 Azure Cognitive Search não indexar alguns documentos no lote, um IndexBatchException será lançado por Documents.Index. Essa 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.

Observação

Você pode usar o método FindFailedActionsToRetry para criar um novo lote contendo somente as ações que falharam em uma chamada anterior para Index. Há uma discussão sobre como usá-lo corretamente no StackOverflow.

Por fim, o método UploadDocuments atrasa por dois segundos. A indexação ocorre de maneira assíncrona em seu serviço Azure Cognitive Search, portanto, o exemplo de aplicativo precisa aguardar alguns instantes a fim de 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.

Como o SDK do .NET lida com documentos

Você pode não saber como o SDK .NET do Azure Cognitive Search é capaz de carregar instâncias de uma classe definida pelo usuário, como Hotel, no índice. Para ajudar a responder a essa pergunta, vamos examinar a classe Hotel :

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Microsoft.Spatial;
using Newtonsoft.Json;

public partial class Hotel
{
    [System.ComponentModel.DataAnnotations.Key]
    [IsFilterable]
    public string HotelId { get; set; }

    [IsSearchable, IsSortable]
    public string HotelName { get; set; }

    [IsSearchable]
    [Analyzer(AnalyzerName.AsString.EnLucene)]
    public string Description { get; set; }

    [IsSearchable]
    [Analyzer(AnalyzerName.AsString.FrLucene)]
    [JsonProperty("Description_fr")]
    public string DescriptionFr { get; set; }

    [IsSearchable, IsFilterable, IsSortable, IsFacetable]
    public string Category { get; set; }

    [IsSearchable, IsFilterable, IsFacetable]
    public string[] Tags { get; set; }

    [IsFilterable, IsSortable, IsFacetable]
    public bool? ParkingIncluded { get; set; }

    // SmokingAllowed reflects whether any room in the hotel allows smoking.
    // The JsonIgnore attribute indicates that a field should not be created 
    // in the index for this property and it will only be used by code in the client.
    [JsonIgnore]
    public bool? SmokingAllowed => (Rooms != null) ? Array.Exists(Rooms, element => element.SmokingAllowed == true) : (bool?)null;

    [IsFilterable, IsSortable, IsFacetable]
    public DateTimeOffset? LastRenovationDate { get; set; }

    [IsFilterable, IsSortable, IsFacetable]
    public double? Rating { get; set; }

    public Address Address { get; set; }

    [IsFilterable, IsSortable]
    public GeographyPoint Location { get; set; }

    public Room[] Rooms { get; set; }
}

A primeira coisa a ser observada é que o nome de cada propriedade pública na classe Hotel é mapeado para um campo com o mesmo nome na definição do índice. Para que cada campo comece com uma letra minúscula ("minúsculas"), informe ao SDK que ele deve mapear os nomes de propriedade automaticamente para minúsculas com o atributo [SerializePropertyNamesAsCamelCase] na classe. Esse cenário é comum em aplicativos .NET que executam a vinculação de dados quando o esquema de destino está fora do controle do desenvolvedor do aplicativo, sem a necessidade de violar as diretrizes de nomenclatura de "maiúsculas e minúsculas" no .NET.

Observação

O SDK .NET do Azure Cognitive Search usa a biblioteca NewtonSoft JSON.NET para serializar e desserializar os objetos do modelo personalizado para JSON. Se necessário, você pode personalizar essa serialização. Para saber mais, consulte Serialização personalizada com JSON.NET.

A segunda coisa a ser observada é que cada propriedade é acrescida de atributos como IsFilterable, IsSearchable, Key e Analyzer. Esses atributos são mapeados diretamente para os atributos de campo correspondentes em um índice do Azure Cognitive Search. A classe FieldBuilder as utiliza para criar definições de campo para o índice.

O terceiro fator importante sobre a classe Hotel são os tipos de dados das propriedades públicas. 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?, Edm.DateTimeOffset etc. As regras específicas para o mapeamento de tipo estão documentadas no método Documents.Get da Referência de SDK .NET do Azure Cognitive Search. A classe FieldBuilder cuida desse mapeamento para você, mas ainda pode ser útil para entender caso você precise solucionar problemas de serialização.

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 o FieldBuilder para não serializá-lo no í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.

Algumas propriedades, como Address e Rooms, são instâncias de classes .NET. Elas representam estruturas de dados mais complexas que, como resultado, exigem campos com um tipo de dados complexo no índice.

A propriedade Address representa um conjunto de vários valores na classe Address, definida abaixo:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Newtonsoft.Json;

namespace AzureSearch.SDKHowTo
{
    public partial class Address
    {
        [IsSearchable]
        public string StreetAddress { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string City { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string StateProvince { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string PostalCode { get; set; }

        [IsSearchable, IsFilterable, IsSortable, IsFacetable]
        public string Country { get; set; }
    }
}

Essa classe contém os valores padrão usados para descrever endereços nos Estados Unidos ou no Canadá. Você pode usar tipos como esse para agrupar campos lógicos no índice.

A propriedade Rooms contém uma matriz de objetos Room:

using System;
using Microsoft.Azure.Search;
using Microsoft.Azure.Search.Models;
using Newtonsoft.Json;

namespace AzureSearch.SDKHowTo
{
    public partial class Room
    {
        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.EnMicrosoft)]
        public string Description { get; set; }

        [IsSearchable]
        [Analyzer(AnalyzerName.AsString.FrMicrosoft)]
        [JsonProperty("Description_fr")]
        public string DescriptionFr { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string Type { get; set; }

        [IsFilterable, IsFacetable]
        public double? BaseRate { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string BedOptions { get; set; }

        [IsFilterable, IsFacetable]
        public int SleepsCount { get; set; }

        [IsFilterable, IsFacetable]
        public bool? SmokingAllowed { get; set; }

        [IsSearchable, IsFilterable, IsFacetable]
        public string[] Tags { get; set; }
    }
}

Seu modelo de dados no .NET e o esquema de índice correspondente devem dar suporte à experiência de pesquisa que você gostaria de fornecer ao usuário final. Cada objeto de nível superior no .NET, como um documento no índice, corresponde a um resultado de pesquisa que você apresentaria na 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. Alguns exemplos de consulta serão apresentados posteriormente.

A capacidade de usar as próprias classes para interagir com documentos no índice também funciona da seguinte maneira: é possível recuperar os resultados da pesquisa e fazer com que o SDK os desserialize automaticamente para um tipo de sua escolha, como abordado na próxima seção.

Observação

O SDK .NET do Azure Cognitive Search também oferece suporte a documentos de tipo dinâmico usando a classe Document, que representa um mapeamento de chave/valor de nomes de campo para valores de campo. Isso é útil em cenários nos quais você não conhece o esquema de índice no momento do design, ou nos quais seria inconveniente associar a classes de modelo específico. Todos os métodos no SDK que lidam com documentos têm sobrecargas que funcionam com a classe Document , bem como sobrecargas fortemente tipadas que utilizam um parâmetro de tipo genérico. Somente as últimas são usadas no exemplo de código deste tutorial. A Document classe herda de Dictionary<string, object>.

Por que você deve usar tipos de dados anuláveis

Ao criar as próprias classes de modelo para mapear para um índice do Azure Cognitive Search, recomenda-se declarar as propriedades de tipos de valor, por exemplo, bool e int, como anuláveis (por exemplo, bool? em vez de bool). Se você usar uma propriedade não anulável, será preciso assegurar que nenhum documento no índice contenha um valor nulo para o campo correspondente. Não será possível obter a ajuda nem do SDK nem do serviço Azure Cognitive Search com isso.

Isso não é apenas uma preocupação hipotética: imagine um cenário em que você adiciona um novo campo a um índice existente do tipo Edm.Int32. Depois de atualizar a definição de índice, todos os documentos terão um valor nulo para esse novo campo (já que todos os tipos são anuláveis no Azure Cognitive Search). Ao usar uma classe de modelo com uma propriedade não anulável int para esse campo, você obterá uma JsonSerializationException como esta ao tentar recuperar os documentos:

Error converting value {null} to type 'System.Int32'. Path 'IntValue'.

Por esse motivo, sugerimos que você use tipos anuláveis nas suas classes de modelo como uma prática recomendada.

Serialização personalizada com JSON.NET

O SDK usa JSON.NET para serializar e desserializar documentos. Você pode personalizar a serialização e a desserialização, quando necessário, definindo seu próprio JsonConverter ou IContractResolver. Para obter mais informações, consulte a Documentação de JSON.NET. Ela pode ser útil ao adaptar uma classe de modelo existente do aplicativo para uso com o Azure Cognitive Search e outros cenários mais avançados. Por exemplo, com a serialização personalizada, você pode:

  • Incluir ou excluir determinadas propriedades da sua classe de modelo para serem armazenadas como campos de documento.
  • Mapear os nomes de propriedade no código aos nomes de campo no índice.
  • Crie atributos personalizados que podem ser usados para mapear propriedades para campos do documento.

Você pode encontrar exemplos de implementação de serialização personalizada nos testes de unidade do SDK .NET do Azure Cognitive Search no GitHub. Um bom ponto de partida é esta pasta. Ela contém classes que são usadas pelos testes de serialização personalizada.

Pesquisando documentos no índice

A última etapa no exemplo de aplicativo é procurar por documentos no índice:

private static void RunQueries(ISearchIndexClient indexClient)
{
    SearchParameters parameters;
    DocumentSearchResult<Hotel> results;

    Console.WriteLine("Search the entire index for the term 'motel' and return only the HotelName field:\n");

    parameters =
        new SearchParameters()
        {
            Select = new[] { "HotelName" }
        };

    results = indexClient.Documents.Search<Hotel>("motel", parameters);

    WriteDocuments(results);

    Console.Write("Apply a filter to the index to find hotels with a room cheaper than $100 per night, ");
    Console.WriteLine("and return the hotelId and description:\n");

    parameters =
        new SearchParameters()
        {
            Filter = "Rooms/any(r: r/BaseRate lt 100)",
            Select = new[] { "HotelId", "Description" }
        };

    results = indexClient.Documents.Search<Hotel>("*", parameters);

    WriteDocuments(results);

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

    parameters =
        new SearchParameters()
        {
            OrderBy = new[] { "LastRenovationDate desc" },
            Select = new[] { "HotelName", "LastRenovationDate" },
            Top = 2
        };

    results = indexClient.Documents.Search<Hotel>("*", parameters);

    WriteDocuments(results);

    Console.WriteLine("Search the entire index for the term 'hotel':\n");

    parameters = new SearchParameters();
    results = indexClient.Documents.Search<Hotel>("hotel", parameters);

    WriteDocuments(results);
}

Cada vez que ele executa uma consulta, esse método cria, primeiro, um novo objeto SearchParameters. Esse objeto é usado para especificar opções adicionais para a consulta, como classificação, filtragem, paginação e facetamento. Nesse método, estamos definindo as propriedades de Filter, Select, OrderBy e Top para consultas diferentes. Todas as propriedades de SearchParameters são documentadas aqui.

A próxima etapa é executar a consulta de pesquisa. A execução da pesquisa é feita com o método Documents.Search. Para cada consulta, passamos o texto de pesquisa a ser usado como uma cadeia de caracteres (ou "*" se não houver um texto de pesquisa) e também os parâmetros de pesquisa criados anteriormente. Também podemos especificar Hotel como o parâmetro de tipo para Documents.Search, que informa ao SDK para desserializar documentos nos resultados da pesquisa em objetos do tipo Hotel.

Observação

Você pode saber mais sobre a sintaxe de expressão da consulta de pesquisa aqui.

Por fim, depois de cada consulta, esse método percorre todas as correspondências nos resultados da pesquisa, imprimindo cada documento no console:

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

    Console.WriteLine();
}

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

parameters =
    new SearchParameters()
    {
        Select = new[] { "HotelName" }
    };

results = indexClient.Documents.Search<Hotel>("motel", parameters);

WriteDocuments(results);

Nesse caso, todo o índice é analisado em busca da palavra "motel" em qualquer campo pesquisável e apenas os nomes de hotéis são recuperados, conforme especificado pelo parâmetro Select. Estes são os resultados:

Name: Secret Point Motel

Name: Twin Dome Motel

A próxima consulta é um pouco mais interessante. Queremos encontrar hotéis com diárias inferiores a US$ 100 e obter resultados somente com o ID e a descrição dos hotéis:

parameters =
    new SearchParameters()
    {
        Filter = "Rooms/any(r: r/BaseRate lt 100)",
        Select = new[] { "HotelId", "Description" }
    };

results = indexClient.Documents.Search<Hotel>("*", parameters);

WriteDocuments(results);

Esta consulta 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. Você pode saber mais sobre a sintaxe do OData com suporte do Azure Cognitive Search aqui.

Estes são alguns dos resultados da consulta:

HotelId: 1
Description: The hotel is ideally located on the main commercial artery of the city in the heart of New York...

HotelId: 2
Description: The hotel is situated in a nineteenth century plaza, which has been expanded and renovated to...

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

parameters =
    new SearchParameters()
    {
        OrderBy = new[] { "LastRenovationDate desc" },
        Select = new[] { "HotelName", "LastRenovationDate" },
        Top = 2
    };

results = indexClient.Documents.Search<Hotel>("*", parameters);

WriteDocuments(results);

Neste caso, usaremos novamente a sintaxe do OData para especificar o parâmetro OrderBy como lastRenovationDate desc. Também vamos definir Top como 2 para garantir que só iremos receber os dois principais documentos. Como antes, definimos Select para especificar quais campos devem ser retornados.

Estes são os resultados:

Name: Fancy Stay        Last renovated on: 6/27/2010 12:00:00 AM +00:00
Name: Roach Motel       Last renovated on: 4/28/1982 12:00:00 AM +00:00

Por fim, queremos encontrar todos os nomes de hotéis que correspondem à palavra "hotel":

parameters = new SearchParameters()
{
    SearchFields = new[] { "HotelName" }
};
results = indexClient.Documents.Search<Hotel>("hotel", parameters);

WriteDocuments(results);

Estes são os resultados, que incluem todos os campos uma vez que não especificamos a propriedade Select:

	HotelId: 3
	Name: Triple Landscape Hotel
	...

Essa etapa conclui o tutorial, mas não pare aqui. **As próximas etapas fornecem recursos adicionais para aprender mais sobre o Azure Cognitive Search.

Próximas etapas