Como usar o Azure Search de um aplicativo .NETHow to use Azure Search from a .NET Application

Importante

Esse conteúdo ainda está em construção.This content is still under construction. Versão 9.0 do SDK do .NET do Azure Search está disponível no NuGet.Version 9.0 of the Azure Search .NET SDK is available on NuGet. Estamos trabalhando para atualizar este guia de migração para explicar como atualizar para a nova versão.We are working on updating this migration guide to explain how to upgrade to the new version. Fique atento.Stay tuned.

Este artigo é um passo a passo para ajudar você a usar o SDK do .NET do Azure Search.This article is a walkthrough to get you up and running with the Azure Search .NET SDK. Você pode usar o SDK do .NET para implementar uma experiência de pesquisa avançada em seu aplicativo usando o Azure Search.You can use the .NET SDK to implement a rich search experience in your application using Azure Search.

Novidades no SDK do Azure SearchWhat's in the Azure Search SDK

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.The SDK consists of a few client libraries that enable you to manage your indexes, data sources, indexers, and synonym maps, as well as upload and manage documents, and execute queries, all without having to deal with the details of HTTP and JSON. Essas bibliotecas de cliente são distribuídas como pacotes NuGet.These client libraries are all distributed as NuGet packages.

O principal pacote NuGet é Microsoft.Azure.Search, o que é um pacote meta que inclui todos os outros pacotes, como dependências.The main NuGet package is Microsoft.Azure.Search, which is a meta-package that includes all the other packages as dependencies. Use esse pacote se você estiver apenas começando ou se você souber que seu aplicativo precisará de todos os recursos do Azure Search.Use this package if you're just getting started or if you know your application will need all the features of Azure Search.

Os outros pacotes NuGet no SDK são:The other NuGet packages in the SDK are:

  • Microsoft.Azure.Search.Data: Use esse pacote se estiver desenvolvendo um aplicativo .NET usando o Azure Search e só precisa consultar ou atualizar documentos nos índices.Microsoft.Azure.Search.Data: Use this package if you're developing a .NET application using Azure Search, and you only need to query or update documents in your indexes. 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.If you also need to create or update indexes, synonym maps, or other service-level resources, use the Microsoft.Azure.Search package instead.
  • Microsoft.Azure.Search.Service: Use esse pacote se estiver desenvolvendo a automação no .NET para gerenciar índices, mapas de sinônimos, indexadores, fontes de dados ou outros recursos no nível do serviço do Azure Search.Microsoft.Azure.Search.Service: Use this package if you're developing automation in .NET to manage Azure Search indexes, synonym maps, indexers, data sources, or other service-level resources. Se você precisar apenas consultar ou atualizar em seus índices, use o Microsoft.Azure.Search.Data pacote em vez disso.If you only need to query or update documents in your indexes, use the Microsoft.Azure.Search.Data package instead. Se você precisar de toda a funcionalidade do Azure Search, use o Microsoft.Azure.Search pacote em vez disso.If you need all the functionality of Azure Search, use the Microsoft.Azure.Search package instead.
  • Microsoft.Azure.Search.Common: Tipos comuns necessários para as bibliotecas .NET do Azure Search.Microsoft.Azure.Search.Common: Common types needed by the Azure Search .NET libraries. Você não precisa usar esse pacote diretamente em seu aplicativo.You do not need to use this package directly in your application. Só deve ser usado como uma dependência.It is only meant to be used as a dependency.

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.The various client libraries define classes like Index, Field, and Document, as well as operations like Indexes.Create and Documents.Search on the SearchServiceClient and SearchIndexClient classes. Essas classes são organizadas nos namespaces a seguir:These classes are organized into the following namespaces:

A versão atual do SDK .Net do Azure Search está disponível para o público geral.The current version of the Azure Search .NET SDK is now generally available. Se você gostaria de fornecer comentários para que possamos incorporar na próxima versão, consulte nosso página de comentários.If you would like to provide feedback for us to incorporate in the next version, see our feedback page.

O SDK do .NET dá suporte à versão 2017-11-11 da API REST da Azure Search.The .NET SDK supports version 2017-11-11 of the Azure Search REST API. Essa versão agora inclui suporte para sinônimos, bem como aprimoramentos incrementais para indexadores.This version now includes support for synonyms, as well as incremental improvements to indexers.

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.This SDK does not support Management Operations such as creating and scaling Search services and managing API keys. Se você precisar gerenciar seus recursos de Pesquisa a partir de um aplicativo .NET, é possível usar o SDK de Gerenciamento do .NET da Azure Search.If you need to manage your Search resources from a .NET application, you can use the Azure Search .NET Management SDK.

Atualizando para a última versão do SDKUpgrading to the latest version of the SDK

Se você já estiver usando uma versão mais antiga do SDK do .NET do Azure Search e quiser atualizar para a versão disponível para o público geral, este artigo explicará como fazer isso.If you're already using an older version of the Azure Search .NET SDK and you'd like to upgrade to the new generally available version, this article explains how.

Requisitos para o SDKRequirements for the SDK

  1. Visual Studio 2017 ou posterior.Visual Studio 2017 or later.
  2. Seu próprio serviço de Azure Search.Your own Azure Search service. Para usar o SDK, você precisará do nome do serviço e de uma ou mais chaves de API.In order to use the SDK, you will need the name of your service and one or more API keys. Criar um serviço no portal ajudará você com estas etapas.Create a service in the portal will help you through these steps.
  3. Baixe o pacote NuGet do SDK do .NET do Azure Search usando "Gerenciar pacotes NuGet" no Visual Studio.Download the Azure Search .NET SDK NuGet package by using "Manage NuGet Packages" in 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).Just search for the package name Microsoft.Azure.Search on NuGet.org (or one of the other package names above if you only need a subset of the functionality).

O SDK do .NET do Azure Search oferece suporte a aplicativos voltados para o .NET Framework 4.5.2 e superior, além do .NET Core.The Azure Search .NET SDK supports applications targeting the .NET Framework 4.5.2 and higher, as well as .NET Core.

Principais cenáriosCore scenarios

Há várias coisas que você precisará fazer em seu aplicativo de pesquisa.There are several things you'll need to do in your search application. Neste tutorial, abordaremos os principais cenários:In this tutorial, we'll cover these core scenarios:

  • Criando um índiceCreating an index
  • Preenchimento do índice com documentosPopulating the index with documents
  • Procura por documentos usando filtros e pesquisa de texto completoSearching for documents using full-text search and filters

O código de exemplo a seguir ilustra cada um desses cenários.The following sample code illustrates each of these scenarios. Fique à vontade para usar os snippets de código em seu próprio aplicativo.Feel free to use the code snippets in your own application.

Visão geralOverview

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.The sample application we'll be exploring creates a new index named "hotels", populates it with a few documents, then executes some search queries. Este é o programa principal, mostrando o fluxo geral:Here is the main program, showing the overall flow:

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

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

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

    ISearchIndexClient indexClient = serviceClient.Indexes.GetClient("hotels");

    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.You can find the full source code of the sample application used in this walk through on GitHub.

Vamos examinar isso passo a passo.We'll walk through this step by step. Primeiro, é necessário criar um novo SearchServiceClient.First we need to create a new SearchServiceClient. Esse objeto permite o gerenciamento de índices.This object allows you to manage indexes. Para construir um, você precisa fornecer o nome do serviço do Azure Search, bem como uma chave de API de administração.In order to construct one, you need to provide your Azure Search service name as well as an admin API key. Você pode inserir essas informações no arquivo appsettings.json do aplicativo de exemplo.You can enter this information in the appsettings.json file of the sample application.

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.If you provide an incorrect key (for example, a query key where an admin key was required), the SearchServiceClient will throw a CloudException with the error message "Forbidden" the first time you call an operation method on it, such as Indexes.Create. Se isso ocorrer, verifique novamente a chave da API.If this happens to you, double-check our API key.

As próximas linhas chamam métodos para criação de um índice chamado "hotéis," excluindo-o primeiro caso ele já exista.The next few lines call methods to create an index named "hotels", deleting it first if it already exists. Abordaremos esses métodos um pouco mais tarde.We will walk through these methods a little later.

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

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

Em seguida, o índice precisa ser preenchido.Next, the index needs to be populated. Para popular o índice, será necessário um SearchIndexClient.To do populate the index, we will need a SearchIndexClient. Há duas maneiras de obter um: construindo ou chamando Indexes.GetClient no SearchServiceClient.There are two ways to obtain one: by constructing it, or by calling Indexes.GetClient on the SearchServiceClient. Usaremos o último por conveniência.We use the latter for convenience.

ISearchIndexClient indexClient = serviceClient.Indexes.GetClient("hotels");

Observação

Em um aplicativo típico de pesquisa, o gerenciamento e o preenchimento do índice é tratado por um componente separado das consultas de pesquisa.In a typical search application, index management and population is handled by a separate component from search queries. Indexes.GetClient é conveniente para preencher um índice porque poupa o trabalho de fornecer outras SearchCredentials.Indexes.GetClient is convenient for populating an index because it saves you the trouble of providing another SearchCredentials. Ele faz isso passando a chave de administrador que você usou para criar o SearchServiceClient ao novo SearchIndexClient.It does this by passing the admin key that you used to create the SearchServiceClient to the new SearchIndexClient. No entanto, na parte do aplicativo que executa consultas, é melhor criar o SearchIndexClient diretamente para que você possa passar uma chave de consulta em vez de uma chave de administrador.However, in the part of your application that executes queries, it is better to create the SearchIndexClient directly so that you can pass in a query key instead of an admin key. Isso está de acordo com o princípio de privilégios mínimos e ajuda a tornar seu aplicativo mais seguro.This is consistent with the principle of least privilege and will help to make your application more secure. Você pode saber mais sobre chaves de administração e chaves de consulta aqui.You can find out more about admin keys and query keys here.

Agora que temos um SearchIndexClient, podemos preencher o índice.Now that we have a SearchIndexClient, we can populate the index. População do índice é feita por outro método que abordaremos mais tarde.Index population is done by another method that we will walk through later.

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

Por fim, executamos algumas consultas de pesquisa e exibimos os resultados.Finally, we execute a few search queries and display the results. Dessa vez, usamos outro SearchIndexClient:This time we use a different SearchIndexClient:

ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(configuration);

RunQueries(indexClientForQueries);

Posteriormente, faremos uma análise mais detalhada do método RunQueries.We will take a closer look at the RunQueries method later. Este é o código para criar o novo SearchIndexClient:Here is the code to create the new SearchIndexClient:

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

    SearchIndexClient indexClient = new SearchIndexClient(searchServiceName, "hotels", 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.This time we use a query key since we do not need write access to the index. Você pode inserir essas informações no arquivo appsettings.json do aplicativo de exemplo.You can enter this information in the appsettings.json file of the sample application.

Se você executar esse aplicativo com um nome de serviço válido e chaves de API, a saída deve ser semelhante a este exemplo:If you run this application with a valid service name and API keys, the output should look like this example:

Deleting index...

Creating index...

Uploading documents...

Waiting for documents to be indexed...

Search the entire index for the term 'budget' and return only the hotelName field:

Name: Roach Motel

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

ID: 2   Description: Cheapest hotel in town
ID: 3   Description: Close to town hall and the river

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: 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

Search the entire index for the term 'motel':

ID: 2   Base rate: 79.99        Description: Cheapest hotel in town     Description (French): Hôtel le moins cher en ville      Name: Roach Motel       Category: Budget        Tags: [motel, budget]   Parking included: yes   Smoking allowed: yes    Last renovated on: 4/28/1982 12:00:00 AM +00:00 Rating: 1/5     Location: Latitude 49.678581, longitude -122.131577

Complete.  Press any key to end application...

O código-fonte completo do aplicativo é fornecido ao final deste artigo.The full source code of the application is provided at the end of this article.

Em seguida, analisaremos com mais detalhes cada um dos métodos chamados pelo Main.Next, we will take a closer look at each of the methods called by Main.

Criando um índiceCreating an index

Depois de criar uma SearchServiceClient, Main exclui o índice "hotéis", caso ele já exista.After creating a SearchServiceClient, Main deletes the "hotels" index if it already exists. Essa exclusão é feito pelo método a seguir:That deletion is done by the following method:

private static void DeleteHotelsIndexIfExists(SearchServiceClient serviceClient)
{
    if (serviceClient.Indexes.Exists("hotels"))
    {
        serviceClient.Indexes.Delete("hotels");
    }
}

Esse método usa o SearchServiceClient fornecido para verificar se o índice existe e, nesse caso, excluí-lo.This method uses the given SearchServiceClient to check if the index exists, and if so, delete it.

Observação

O exemplo código deste artigo usa os métodos síncronos do SDK do .NET do Azure Search para manter a simplicidade.The example code in this article uses the synchronous methods of the Azure Search .NET SDK for simplicity. Recomendamos que você use os métodos assíncronos em seus próprios aplicativos para mantê-los escalonáveis e responsivos.We recommend that you use the asynchronous methods in your own applications to keep them scalable and responsive. Por exemplo, no método acima você pode usar ExistsAsync e DeleteAsync em vez de Exists e Delete.For example, in the method above you could use ExistsAsync and DeleteAsync instead of Exists and Delete.

Em seguida, Main cria um novo índice "hotéis" chamando este método:Next, Main creates a new "hotels" index by calling this method:

private static void CreateHotelsIndex(SearchServiceClient serviceClient)
{
    var definition = new Index()
    {
        Name = "hotels",
        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.This method creates a new Index object with a list of Field objects that defines the schema of the new index. Cada campo tem um nome, tipo de dados e vários atributos que definem seu comportamento de pesquisa.Each field has a name, data type, and several attributes that define its search behavior. 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.The FieldBuilder class uses reflection to create a list of Field objects for the index by examining the public properties and attributes of the given Hotel model class. Posteriormente, faremos uma análise mais detalhada da classe Hotel.We'll take a closer look at the Hotel class later on.

Observação

Você pode criar quando desejar a lista de objetos Field diretamente, em vez de usar o FieldBuilder se necessário.You can always create the list of Field objects directly instead of using FieldBuilder if needed. 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.For example, you may not want to use a model class or you may need to use an existing model class that you don't want to modify by adding attributes.

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 são omitidos do exemplo para fins de brevidade).In addition to fields, you can also add scoring profiles, suggesters, or CORS options to the Index (these parameters are omitted from the sample for brevity). Você pode saber mais sobre o objeto Index e suas partes na referência do SDK, bem como na Referência da API REST do Azure Search.You can find more information about the Index object and its constituent parts in the SDK reference, as well as in the Azure Search REST API reference.

Preenchendo o índicePopulating the index

A próxima etapa no Main é popular o índice recém-criado.The next step in Main is to populate the newly-created index. A população do índice é feita no método a seguir:This index population is done in the following method:

private static void UploadDocuments(ISearchIndexClient indexClient)
{
    var hotels = new Hotel[]
    {
        new Hotel()
        { 
            HotelId = "1", 
            BaseRate = 199.0, 
            Description = "Best hotel in town",
            DescriptionFr = "Meilleur hôtel en ville",
            HotelName = "Fancy Stay",
            Category = "Luxury", 
            Tags = new[] { "pool", "view", "wifi", "concierge" },
            ParkingIncluded = false, 
            SmokingAllowed = false,
            LastRenovationDate = new DateTimeOffset(2010, 6, 27, 0, 0, 0, TimeSpan.Zero), 
            Rating = 5, 
            Location = GeographyPoint.Create(47.678581, -122.131577)
        },
        new Hotel()
        { 
            HotelId = "2", 
            BaseRate = 79.99,
            Description = "Cheapest hotel in town",
            DescriptionFr = "Hôtel le moins cher en ville",
            HotelName = "Roach Motel",
            Category = "Budget",
            Tags = new[] { "motel", "budget" },
            ParkingIncluded = true,
            SmokingAllowed = true,
            LastRenovationDate = new DateTimeOffset(1982, 4, 28, 0, 0, 0, TimeSpan.Zero),
            Rating = 1,
            Location = GeographyPoint.Create(49.678581, -122.131577)
        },
        new Hotel() 
        { 
            HotelId = "3", 
            BaseRate = 129.99,
            Description = "Close to town hall and the river"
        }
    };

    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.This method has four parts. O primeiro cria uma matriz de objetos Hotel que servirá como nossos dados de entrada para carregamento do índice.The first creates an array of Hotel objects that will serve as our input data to upload to the index. Esses dados são codificados para manter a simplicidade.This data is hard-coded for simplicity. Em seu próprio aplicativo, provavelmente seus dados virão de uma fonte de dados externa, como um banco de dados SQL.In your own application, your data will likely come from an external data source such as a SQL database.

A segunda parte cria um IndexBatch com os documentos.The second part creates an IndexBatch containing the documents. Especifique a operação que você quer aplicar ao lote no momento da criação dele, nesse caso, chamando IndexBatch.Upload.You specify the operation you want to apply to the batch at the time you create it, in this case by calling IndexBatch.Upload. O lote é então carregado para o índice do Azure Search pelo método Documents.Index .The batch is then uploaded to the Azure Search index by the Documents.Index method.

Observação

Neste exemplo, estamos carregando apenas documentos.In this example, we are just uploading documents. Se você quiser mesclar alterações em documentos existentes ou excluir documentos, poderá criar lotes chamando IndexBatch.Merge, IndexBatch.MergeOrUpload ou IndexBatch.Delete.If you wanted to merge changes into existing documents or delete documents, you could create batches by calling IndexBatch.Merge, IndexBatch.MergeOrUpload, or IndexBatch.Delete instead. Também é possível combinar diferentes operações em um único lote chamando IndexBatch.New, que usa uma coleção de objetos IndexAction, com cada um deles informando ao Azure Search para executar uma operação específica em um documento.You can also mix different operations in a single batch by calling IndexBatch.New, which takes a collection of IndexAction objects, each of which tells Azure Search to perform a particular operation on a document. 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.You can create each IndexAction with its own operation by calling the corresponding method such as IndexAction.Merge, IndexAction.Upload, and so on.

A terceira parte desse método é um bloco catch que trata um caso de erro importante para indexação.The third part of this method is a catch block that handles an important error case for indexing. Se o serviço de Azure Search não indexar alguns documentos no lote, uma IndexBatchException será lançada por Documents.Index.If your Azure Search service fails to index some of the documents in the batch, an IndexBatchException is thrown by Documents.Index. Essa exceção poderá ocorrer se você estiver indexando documentos enquanto o serviço estiver sob carga pesada.This exception can happen if you are indexing documents while your service is under heavy load. É altamente recomendável a manipulação explícita desse caso em seu código.We strongly recommend explicitly handling this case in your code. 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.You can delay and then retry indexing the documents that failed, or you can log and continue like the sample does, or you can do something else depending on your application's data consistency requirements.

Observação

Você pode usar o método FindFailedActionsToRetry para construir um novo lote contendo apenas as ações que falharam em uma chamada anterior para Index.You can use the FindFailedActionsToRetry method to construct a new batch containing only the actions that failed in a previous call to Index. Há uma discussão sobre como usá-lo corretamente no StackOverflow.There is a discussion of how to properly use it on StackOverflow.

Por fim, o método UploadDocuments atrasa por dois segundos.Finally, the UploadDocuments method delays for two seconds. A indexação ocorre de maneira assíncrona em seu serviço de Azure Search, portanto, o exemplo de aplicativo precisa aguardar alguns instantes para garantir que os documentos estejam disponíveis para pesquisa.Indexing happens asynchronously in your Azure Search service, so the sample application needs to wait a short time to ensure that the documents are available for searching. Normalmente, atrasos como esses só são necessários em demonstrações, testes e exemplos de aplicativos.Delays like this are typically only necessary in demos, tests, and sample applications.

Como o SDK do .NET lida com documentosHow the .NET SDK handles documents

Você pode estar se perguntando como o SDK do .NET do Azure Search é capaz de carregar instâncias de uma classe definida pelo usuário, como Hotel , no índice.You may be wondering how the Azure Search .NET SDK is able to upload instances of a user-defined class like Hotel to the index. Para ajudar a responder a essa pergunta, vamos examinar a classe Hotel :To help answer that question, let's look at the Hotel class:

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

// The SerializePropertyNamesAsCamelCase attribute is defined in the Azure Search .NET SDK.
// It ensures that Pascal-case property names in the model class are mapped to camel-case
// field names in the index.
[SerializePropertyNamesAsCamelCase]
public partial class Hotel
{
    [System.ComponentModel.DataAnnotations.Key]
    [IsFilterable]
    public string HotelId { get; set; }

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

    [IsSearchable]
    public string Description { get; set; }

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

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

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

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

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

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

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

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

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

A primeira coisa a observar é que cada propriedade pública de Hotel corresponde a um campo na definição do índice, mas com uma diferença fundamental: o nome de cada campo começa com uma letra minúscula ("minúsculas concatenadas"), enquanto o nome de cada propriedade pública de Hotel começa com uma letra maiúscula ("maiúsculas concatenadas").The first thing to notice is that each public property of Hotel corresponds to a field in the index definition, but with one crucial difference: The name of each field starts with a lower-case letter ("camel case"), while the name of each public property of Hotel starts with an upper-case letter ("Pascal case"). Esse cenário é comum em aplicativos .NET que executam associação de dados em que o esquema de destino está fora do controle do desenvolvedor do aplicativo.This scenario is common in .NET applications that perform data-binding where the target schema is outside the control of the application developer. Em vez de violar as diretrizes de nomenclatura do .NET, usando minúscula para os nomes de propriedade, você pode informar ao SDK para mapear automaticamente os nomes de propriedade como minúscula com o atributo [SerializePropertyNamesAsCamelCase] .Rather than having to violate the .NET naming guidelines by making property names camel-case, you can tell the SDK to map the property names to camel-case automatically with the [SerializePropertyNamesAsCamelCase] attribute.

Observação

O SDK do .NET de Azure Search usa a biblioteca NewtonSoft JSON.NET para serializar e desserializar os objetos de modelo personalizados para e a partir do JSON.The Azure Search .NET SDK uses the NewtonSoft JSON.NET library to serialize and deserialize your custom model objects to and from JSON. Se necessário, você pode personalizar essa serialização.You can customize this serialization if needed. Para obter mais informações, consulte serialização personalizada com JSON.NET.For more information, see Custom Serialization with JSON.NET.

A segunda coisa a observar é os atributos que decoram cada propriedade pública (como IsFilterable, IsSearchable, Key, e Analyzer).The second thing to notice is the attributes that decorate each public property (such as IsFilterable, IsSearchable, Key, and Analyzer). Esses atributos são mapeados diretamente para os atributos correspondentes do índice da Azure Search.These attributes map directly to the corresponding attributes of the Azure Search index. O FieldBuilder classe usa essas propriedades para criar definições de campo para o índice.The FieldBuilder class uses these properties to construct field definitions for the index.

O terceiro fator importante sobre o Hotel classe é os tipos de dados das propriedades públicas.The third important thing about the Hotel class is the data types of the public properties. Os tipos .NET dessas propriedades estão mapeados para seus tipos de campo equivalentes na definição do índice.The .NET types of these properties map to their equivalent field types in the index definition. Por exemplo, a propriedade de cadeia de caracteres Category mapeia para o campo category, que é do tipo Edm.String.For example, the Category string property maps to the category field, which is of type Edm.String. Há mapeamentos de tipo semelhantes entre bool? e Edm.Boolean, DateTimeOffset? e Edm.DateTimeOffset etc. As regras específicas para o mapeamento de tipos estão documentadas com o método Documents.Get na referência do SDK do .NET do Azure Search.There are similar type mappings between bool? and Edm.Boolean, DateTimeOffset? and Edm.DateTimeOffset, etc. The specific rules for the type mapping are documented with the Documents.Get method in the Azure Search .NET SDK reference. A classe FieldBuilder cuida desse mapeamento para você, mas ainda pode ser útil para entender caso você precise solucionar problemas de serialização.The FieldBuilder class takes care of this mapping for you, but it can still be helpful to understand in case you need to troubleshoot any serialization issues.

Essa capacidade de usar suas próprias classes como documentos funciona em ambas as direções; Você também pode recuperar os resultados da pesquisa e fazer com que o SDK os desserialize automaticamente para um tipo de sua escolha, como veremos na próxima seção.This ability to use your own classes as documents works in both directions; You can also retrieve search results and have the SDK automatically deserialize them to a type of your choice, as we will see in the next section.

Observação

O SDK do .NET do Azure Search também oferece suporte a documentos do tipo dinâmico usando a classe Document, que é um mapeamento de chave/valor de nomes de campo para valores de campo.The Azure Search .NET SDK also supports dynamically-typed documents using the Document class, which is a key/value mapping of field names to field values. 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.This is useful in scenarios where you don't know the index schema at design-time, or where it would be inconvenient to bind to specific model classes. 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.All the methods in the SDK that deal with documents have overloads that work with the Document class, as well as strongly-typed overloads that take a generic type parameter. Somente as últimas são usadas no exemplo de código deste tutorial.Only the latter are used in the sample code in this tutorial. A Document classe herda de Dictionary<string, object>.The Document class inherits from Dictionary<string, object>.

Por que você deve usar tipos de dados anuláveisWhy you should use nullable data types

Ao criar suas próprias classes de modelo para mapear para um índice de Azure Search, sugerimos declarar as propriedades dos tipos de valor como bool e int para serem anuláveis (por exemplo, bool? em vez de bool).When designing your own model classes to map to an Azure Search index, we recommend declaring properties of value types such as bool and int to be nullable (for example, bool? instead of 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.If you use a non-nullable property, you have to guarantee that no documents in your index contain a null value for the corresponding field. Nem o SDK, nem o serviço Azure Search ajudarão você a impor isso.Neither the SDK nor the Azure Search service will help you to enforce this.

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.This is not just a hypothetical concern: Imagine a scenario where you add a new field to an existing index that is of type 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 Search).After updating the index definition, all documents will have a null value for that new field (since all types are nullable in Azure 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:If you then use a model class with a non-nullable int property for that field, you will get a JsonSerializationException like this when trying to retrieve documents:

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.For this reason, we recommend that you use nullable types in your model classes as a best practice.

Serialização personalizada com JSON.NETCustom Serialization with JSON.NET

O SDK usa JSON.NET para serializar e desserializar documentos.The SDK uses JSON.NET for serializing and deserializing documents. Você pode personalizar a serialização e desserialização, se necessário, definindo seu próprio JsonConverter ou IContractResolver.You can customize serialization and deserialization if needed by defining your own JsonConverter or IContractResolver. Para obter mais informações, consulte o documentação do JSON.NET.For more information, see the JSON.NET documentation. Isso pode ser útil quando você quer adaptar uma classe de modelo existente de seu aplicativo para usar com o Azure Search e outros cenários mais avançados.This can be useful when you want to adapt an existing model class from your application for use with Azure Search, and other more advanced scenarios. Por exemplo, com a serialização personalizada, você pode:For example, with custom serialization you can:

  • Incluir ou excluir determinadas propriedades da sua classe de modelo para serem armazenadas como campos de documento.Include or exclude certain properties of your model class from being stored as document fields.
  • Mapear os nomes de propriedade no código aos nomes de campo no índice.Map between property names in your code and field names in your index.
  • Crie atributos personalizados que podem ser usados para mapear propriedades para campos do documento.Create custom attributes that can be used for mapping properties to document fields.

Você pode encontrar exemplos de implementação de serialização personalizada nos testes de unidade para o SDK .NET do Azure Search no GitHub.You can find examples of implementing custom serialization in the unit tests for the Azure Search .NET SDK on GitHub. Um bom ponto de partida é esta pasta.A good starting point is this folder. Ela contém classes que são usadas pelos testes de serialização personalizada.It contains classes that are used by the custom serialization tests.

Pesquisando documentos no índiceSearching for documents in the index

A última etapa no aplicativo de exemplo é procurar por documentos no índice:The last step in the sample application is to search for some documents in the index:

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

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

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

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

    WriteDocuments(results);

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

    parameters =
        new SearchParameters()
        {
            Filter = "baseRate lt 150",
            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 'motel':\n");

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

    WriteDocuments(results);
}

Cada vez que ele executa uma consulta, esse método cria, primeiro, um novo objeto SearchParameters.Each time it executes a query, this method first creates a new SearchParameters object. Esse objeto é usado para especificar opções adicionais para a consulta como classificação, filtragem, paginação e facetas.This object is used to specify additional options for the query such as sorting, filtering, paging, and faceting. Nesse método, estamos definindo as propriedades de Filter, Select, OrderBy e Top para consultas diferentes.In this method, we're setting the Filter, Select, OrderBy, and Top property for different queries. Todas as propriedades de SearchParameters são documentadas aqui.All the SearchParameters properties are documented here.

A próxima etapa é executar a consulta de pesquisa.The next step is to actually execute the search query. Executando a pesquisa é feita usando o Documents.Search método.Running the search is done using the Documents.Search method. 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.For each query, we pass the search text to use as a string (or "*" if there is no search text), plus the search parameters created earlier. 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.We also specify Hotel as the type parameter for Documents.Search, which tells the SDK to deserialize documents in the search results into objects of type Hotel.

Observação

Você pode saber mais sobre a sintaxe de expressão da consulta de pesquisa aqui.You can find more information about the search query expression syntax here.

Por fim, depois de cada consulta, esse método percorre todas as correspondências nos resultados da pesquisa, imprimindo cada documento no console:Finally, after each query this method iterates through all the matches in the search results, printing each document to the 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.Let's take a closer look at each of the queries in turn. Este é o código para executar a primeira consulta:Here is the code to execute the first query:

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

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

WriteDocuments(results);

Neste caso, estamos pesquisando hotéis que correspondam à palavra "budget" e queremos nos resultados apenas os nomes dos hotéis, conforme especificado pelo parâmetro Select.In this case, we're searching for hotels that match the word "budget", and we want to get back only the hotel names, as specified by the Select parameter. Estes são os resultados:Here are the results:

Name: Roach Motel

Em seguida, queremos encontrar hotéis com uma diária inferior a US$ 150 e obter resultados somente com a ID e descrição dos hotéis:Next, we want to find the hotels with a nightly rate of less than $150, and return only the hotel ID and description:

parameters =
    new SearchParameters()
    {
        Filter = "baseRate lt 150",
        Select = new[] { "hotelId", "description" }
    };

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

WriteDocuments(results);

Esta consulta usa uma expressão $filter do OData, baseRate lt 150, para filtrar os documentos no índice.This query uses an OData $filter expression, baseRate lt 150, to filter the documents in the index. Você pode saber mais sobre a sintaxe do OData com suporte do Azure Search aqui.You can find out more about the OData syntax that Azure Search supports here.

Estes são alguns dos resultados da consulta:Here are the results of the query:

ID: 2   Description: Cheapest hotel in town
ID: 3   Description: Close to town hall and the river

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.Next, we want to find the top two hotels that have been most recently renovated, and show the hotel name and last renovation date. Eis o código:Here is the code:

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.In this case, we again use OData syntax to specify the OrderBy parameter as lastRenovationDate desc. Também vamos definir Top como 2 para garantir que só iremos receber os dois principais documentos.We also set Top to 2 to ensure we only get the top two documents. Como antes, definimos Select para especificar quais campos devem ser retornados.As before, we set Select to specify which fields should be returned.

Estes são os resultados:Here are the results:

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 hotéis que correspondam à palavra "motel":Finally, we want to find all hotels that match the word "motel":

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

WriteDocuments(results);

Estes são os resultados, que incluem todos os campos uma vez que não especificamos a propriedade Select:And here are the results, which include all fields since we did not specify the Select property:

ID: 2   Base rate: 79.99        Description: Cheapest hotel in town     Description (French): Hôtel le moins cher en ville      Name: Roach Motel       Category: Budget        Tags: [motel, budget]   Parking included: yes   Smoking allowed: yes    Last renovated on: 4/28/1982 12:00:00 AM +00:00 Rating: 1/5     Location: Latitude 49.678581, longitude -122.131577

Essa etapa conclui o tutorial, mas não pare aqui.This step completes the tutorial, but don't stop here. * * Próximas etapas fornecem recursos adicionais para aprender mais sobre o Azure Search.**Next steps provide additional resources for learning more about Azure Search.

Próximas etapasNext steps