Cómo usar Azure Search desde una aplicación .NETHow to use Azure Search from a .NET Application

Este artículo es un tutorial para empezar a trabajar con el SDK de Azure Search para .NET.This article is a walkthrough to get you up and running with the Azure Search .NET SDK. Puede utilizar el SDK para .NET para implementar una experiencia de búsqueda enriquecida en la aplicación mediante Azure Search.You can use the .NET SDK to implement a rich search experience in your application using Azure Search.

Qué es el SDK de Azure SearchWhat's in the Azure Search SDK

El SDK se compone de unas cuantas bibliotecas de cliente que le permiten administrar los índices, orígenes de datos, indexadores y asignaciones de sinónimos, así como cargar y administrar documentos y ejecutar consultas; todo ello sin tener que lidiar con los detalles de HTTP y 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. Esas bibliotecas de cliente se distribuyen como paquetes de NuGet.These client libraries are all distributed as NuGet packages.

El paquete de NuGet principal es Microsoft.Azure.Search, que es un metapaquete que incluye el resto de paquetes a modo de dependencias.The main NuGet package is Microsoft.Azure.Search, which is a meta-package that includes all the other packages as dependencies. Use este paquete si está comenzando a trabajar con él o si sabe que la aplicación necesitará todas las características de 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.

Estos son otros paquetes de NuGet que tiene el SDK:The other NuGet packages in the SDK are:

  • Microsoft.Azure.Search.Data: use este paquete si va a desarrollar una aplicación de .NET mediante Azure Search y solo necesita consultar o actualizar los documentos en los í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. Si también tiene que crear o actualizar los índices, las asignaciones de sinónimos u otros recursos de nivel de servicio, use el paquete Microsoft.Azure.Search.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 este paquete si está desarrollando la automatización en .NET para administrar los índices, las asignaciones de sinónimos, los indexadores, los orígenes de datos u otros recursos de nivel de servicio de 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. Si solo necesita consultar o actualizar los documentos de los índices, use el paquete Microsoft.Azure.Search.Data en su lugar.If you only need to query or update documents in your indexes, use the Microsoft.Azure.Search.Data package instead. Si necesita toda la funcionalidad de Azure Search, use el paquete Microsoft.Azure.Search.If you need all the functionality of Azure Search, use the Microsoft.Azure.Search package instead.
  • Microsoft.Azure.Search.Common: tipos comunes que necesitan las bibliotecas de .NET de Azure Search.Microsoft.Azure.Search.Common: Common types needed by the Azure Search .NET libraries. No es necesario usar este paquete directamente en la aplicación,You do not need to use this package directly in your application. sino que está concebido únicamente para usarse como una dependencia.It is only meant to be used as a dependency.

Las diferentes bibliotecas de cliente definen clases como Index, Field y Document, además de operaciones como Indexes.Create y Documents.Search en las clases SearchServiceClient y 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. Estas clases están organizadas en los espacios de nombres siguientes:These classes are organized into the following namespaces:

Si desea proporcionar comentarios para una actualización futura del SDK, vaya a nuestra página de comentarios o cree un problema en GitHub y mencione "Azure Search" en el título del problema.If you would like to provide feedback for a future update of the SDK, see our feedback page or create an issue on GitHub and mention "Azure Search" in the issue title.

El SDK para .NET es compatible con la versión 2019-05-06 de la API de REST de Azure Search.The .NET SDK supports version 2019-05-06 of the Azure Search REST API. Esta versión incluye compatibilidad con tipos complejos, con la búsqueda cognitiva, con la característica Autocompletar y con el modo de análisis JsonLines al indexar Azure Blobs.This version includes support for complex types, cognitive search, autocomplete, and JsonLines parsing mode when indexing Azure Blobs.

Este SDK no admite operaciones de administración como la creación y el escalado de servicios de Search y las claves de API de administración.This SDK does not support Management Operations such as creating and scaling Search services and managing API keys. Si necesita administrar los recursos de Search desde una aplicación .NET, puede usar el SDK de administración para .NET de Azure Search.If you need to manage your Search resources from a .NET application, you can use the Azure Search .NET Management SDK.

Actualización a la versión más reciente del SDKUpgrading to the latest version of the SDK

Si ya utiliza una versión anterior del SDK de .NET para Azure Search y desea actualizar a la versión disponible más reciente, en este artículo se explica el proceso.If you're already using an older version of the Azure Search .NET SDK and you'd like to upgrade to the latest generally available version, this article explains how.

Requisitos para el SDKRequirements for the SDK

  1. Visual Studio 2017 o cualquier versión posterior.Visual Studio 2017 or later.
  2. Su propio servicio Azure Search.Your own Azure Search service. Para usar el SDK, será necesario el nombre del servicio y una o varias claves de API.In order to use the SDK, you will need the name of your service and one or more API keys. Crear un servicio en el portal le ayudará con estos pasos.Create a service in the portal will help you through these steps.
  3. Descargue el paquete NuGet del SDK de Azure Search para .NET mediante "Administrar paquetes de NuGet" en Visual Studio.Download the Azure Search .NET SDK NuGet package by using "Manage NuGet Packages" in Visual Studio. Simplemente busque el nombre del paquete Microsoft.Azure.Search en NuGet.org (o uno de los otros nombres de paquetes mencionados anteriormente si solo necesita un subconjunto de la funcionalidad).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).

El SDK de .NET de Azure Search es compatible con las aplicaciones destinadas a .NET Framework 4.5.2 y otras versiones superiores, así como a .NET Core 2.0 y otras versiones superiores.The Azure Search .NET SDK supports applications targeting the .NET Framework 4.5.2 and higher, as well as .NET Core 2.0 and higher.

Escenarios principalesCore scenarios

Hay varias tareas que debe realizar en su aplicación de búsqueda.There are several things you'll need to do in your search application. En este tutorial, hablaremos sobre estos escenarios básicos:In this tutorial, we'll cover these core scenarios:

  • Creación de un índiceCreating an index
  • Llenado del índice con documentosPopulating the index with documents
  • Búsqueda de documentos mediante filtros y búsqueda de texto completoSearching for documents using full-text search and filters

El código de ejemplo siguiente ilustra cada uno de estos escenarios.The following sample code illustrates each of these scenarios. No dude en usar los fragmentos de código en su propia aplicación.Feel free to use the code snippets in your own application.

Información generalOverview

La aplicación de ejemplo que vamos a explorar crea un nuevo índice denominado "hotels", lo rellena con varios documentos y, a continuación, ejecuta varias consultas de búsqueda.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 es el programa principal, que muestra el flujo general: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);

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

Nota

Puede encontrar el código fuente completo de la aplicación de ejemplo usada en este tutorial en GitHub.You can find the full source code of the sample application used in this walk through on GitHub.

Lo recorreremos paso a paso.We'll walk through this step by step. Primero, debemos crear un nuevo SearchServiceClient.First we need to create a new SearchServiceClient. Este objeto le permite administrar índices.This object allows you to manage indexes. Para crear uno, deberá proporcionar el nombre de su servicio Azure Search y una clave de API de administración.In order to construct one, you need to provide your Azure Search service name as well as an admin API key. Puede escribir esta información en el archivo appsettings.json de la aplicación de ejemplo.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;
}

Nota

Si proporciona una clave incorrecta (por ejemplo, una clave de consulta cuando se requiere una clave de administración), el SearchServiceClient producirá un CloudException con el mensaje de error "Forbidden" (Prohibido) cuando llame a un método de operación con él, 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. Si esto sucede, compruebe la clave de API.If this happens to you, double-check our API key.

Las siguientes líneas llaman a métodos para crear un índice llamado "hotels", eliminándolo antes si ya existe.The next few lines call methods to create an index named "hotels", deleting it first if it already exists. Describiremos estos métodos más adelante.We will walk through these methods a little later.

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

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

A continuación, el índice debe rellenarse.Next, the index needs to be populated. Para rellenar el índice, necesitamos un objeto SearchIndexClient.To do populate the index, we will need a SearchIndexClient. Hay dos maneras de obtener uno: se puede crear o se puede llamar a Indexes.GetClient en el SearchServiceClient.There are two ways to obtain one: by constructing it, or by calling Indexes.GetClient on the SearchServiceClient. Usamos esta última por motivos de comodidad.We use the latter for convenience.

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

Nota

En una aplicación de búsqueda típica, el llenado y la administración de índices se pueden controlar mediante un componente independiente de las consultas de búsqueda.In a typical search application, index management and population may be handled by a separate component from search queries. Indexes.GetClient resulta cómodo para rellenar un índice, porque evita la molestia de dar otro SearchCredentials más.Indexes.GetClient is convenient for populating an index because it saves you the trouble of providing additional SearchCredentials. Con este fin, pasa la clave de administrador que se usó para crear el SearchServiceClient al nuevo SearchIndexClient.It does this by passing the admin key that you used to create the SearchServiceClient to the new SearchIndexClient. Sin embargo, en la parte de la aplicación que ejecuta consultas, es mejor crear directamente el objeto SearchIndexClient para poder pasar una clave de consulta (que solo permite leer datos) en lugar de una clave de administración.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, which only allows you to read data, instead of an admin key. Esto está en consonancia con el principio de privilegios mínimos y le ayudará a proteger su aplicación.This is consistent with the principle of least privilege and will help to make your application more secure. Puede encontrar más información acerca de las claves de administración y consulta aquí.You can find out more about admin keys and query keys here.

Ahora que tenemos un SearchIndexClient, podemos rellenar el índice.Now that we have a SearchIndexClient, we can populate the index. Para ello, utilizaremos otro método que trataremos más adelante.Index population is done by another method that we will walk through later.

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

Finalmente, ejecutamos algunas consultas de búsqueda y mostramos los resultados.Finally, we execute a few search queries and display the results. Esta vez usamos otro SearchIndexClient:This time we use a different SearchIndexClient:

ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(indexName, configuration);

RunQueries(indexClientForQueries);

Echaremos un vistazo al método RunQueries más adelante.We will take a closer look at the RunQueries method later. Este es el código para crear el nuevo elemento SearchIndexClient:Here is the code to create the new 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;
}

Esta vez usamos una clave de consulta dado que no necesitamos acceso de escritura al índice.This time we use a query key since we do not need write access to the index. Puede escribir esta información en el archivo appsettings.json de la aplicación de ejemplo.You can enter this information in the appsettings.json file of the sample application.

Si ejecuta esta aplicación con un nombre de servicio válido y claves de API, la salida será parecida a la de este ejemplo (parte de la salida de la consola se ha reemplazado por "..." con fines meramente ilustrativos):If you run this application with a valid service name and API keys, the output should look like this example: (Some console output has been replaced with "..." for illustration purposes.)

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

El código fuente completo de la aplicación se proporciona al final de este artículo.The full source code of the application is provided at the end of this article.

A continuación, veremos más de cerca cada uno de los métodos llamados por Main.Next, we will take a closer look at each of the methods called by Main.

Creación de un índiceCreating an index

Después de crear un objeto SearchServiceClient, Main es eliminar el índice "hotels", si ya existe.After creating a SearchServiceClient, Main deletes the "hotels" index if it already exists. Esta eliminación se lleva a cabo mediante el método siguiente:That deletion is done by the following method:

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

Este método utiliza el SearchServiceClient proporcionado para comprobar si existe el índice y, en ese caso, eliminarlo.This method uses the given SearchServiceClient to check if the index exists, and if so, delete it.

Nota

El código de ejemplo de este artículo usa los métodos sincrónicos del SDK de Azure Search para .NET por motivos de simplicidad.The example code in this article uses the synchronous methods of the Azure Search .NET SDK for simplicity. En sus propias aplicaciones, recomendamos que use métodos asincrónicos para mantener su escalabilidad y capacidad de respuesta.We recommend that you use the asynchronous methods in your own applications to keep them scalable and responsive. Por ejemplo, en el método anterior podría utilizar ExistsAsync y DeleteAsync en lugar de Exists y Delete.For example, in the method above you could use ExistsAsync and DeleteAsync instead of Exists and Delete.

A continuación, Main crea un nuevo índice "hotels" mediante una llamada a este método:Next, Main creates a new "hotels" index by calling this method:

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

Este método crea un nuevo objeto Index con una lista de objetos Field que define el esquema del nuevo índice.This method creates a new Index object with a list of Field objects that defines the schema of the new index. Cada campo tiene un nombre, un tipo de datos y varios atributos que definen su comportamiento de búsqueda.Each field has a name, data type, and several attributes that define its search behavior. La clase FieldBuilder usa reflexión para crear una lista de objetos Field para el índice examinando las propiedades públicas y los atributos de la clase de modelo Hotel dada.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. Examinaremos más de cerca la clase Hotel más adelante.We'll take a closer look at the Hotel class later on.

Nota

En caso necesario, siempre puede crear la lista de objetos Field directamente en lugar de usar FieldBuilder.You can always create the list of Field objects directly instead of using FieldBuilder if needed. Por ejemplo, puede que no quiera usar una clase model o que necesite usar una clase model existente que no quiere modificar agregando 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.

Además de campos, puede agregar al índice perfiles de puntuación, proveedores de sugerencias u opciones de CORS (estos parámetros se omiten en el ejemplo para mayor brevedad).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). Puede encontrar más información sobre el objeto Index y sus partes constituyentes en la referencia del SDK, así como en la referencia de la API de REST de 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.

Llenado del índicePopulating the index

El siguiente paso en Main rellena el índice recién creado.The next step in Main populates the newly-created index. Esto se lleva a cabo mediante el método siguiente (parte del código se ha reemplazado por "..." con fines meramente ilustrativos,This index population is done in the following method: (Some code replaced with "..." for illustration purposes. pero puede consultar la solución de ejemplo completa para ver el código de llenado de datos completo):See the full sample solution for the full data population code.)

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 tiene cuatro partes.This method has four parts. La primera crea una matriz de tres objetos Hotel (cada uno con tres objetos Room), que serán los datos de entrada que cargaremos en el índice.The first creates an array of 3 Hotel objects each with 3 Room objects that will serve as our input data to upload to the index. Estos datos están incluidos en el código por motivos de simplicidad.This data is hard-coded for simplicity. En su propia aplicación, los datos provendrán normalmente de un origen de datos externo, como una base de datos SQL.In your own application, your data will likely come from an external data source such as a SQL database.

En la segunda parte se crea un IndexBatch que contiene los documentos.The second part creates an IndexBatch containing the documents. Especifique la operación que desee aplicar al lote en el momento de su creación, en este caso llamando a 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. A continuación, el lote se carga en el índice de Azure Search mediante el método Documents.Index .The batch is then uploaded to the Azure Search index by the Documents.Index method.

Nota

En este ejemplo nos limitamos a cargar documentos.In this example, we are just uploading documents. Si desea combinar los cambios en los documentos existentes o eliminar documentos, puede crear lotes llamando a IndexBatch.Merge, IndexBatch.MergeOrUpload o 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. También puede combinar diferentes operaciones en un único lote llamando a IndexBatch.New, que selecciona una colección de objetos de IndexAction, que indican a Azure Search que realice una operación determinada en un 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. Puede crear cada IndexAction con su propia operación llamando al método correspondiente, como IndexAction.Merge, IndexAction.Upload, etc.You can create each IndexAction with its own operation by calling the corresponding method such as IndexAction.Merge, IndexAction.Upload, and so on.

La tercera parte de este método es un bloque catch que controla un caso de error importante para la indización.The third part of this method is a catch block that handles an important error case for indexing. Si su servicio Azure Search no logra indizar algunos de los documentos del lote, aparece una IndexBatchException producida 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. Esta excepción puede suceder si se indexan documentos mientras el servicio está sobrecargado.This exception can happen if you are indexing documents while your service is under heavy load. Recomendamos encarecidamente controlar este caso de forma explícita en el código.We strongly recommend explicitly handling this case in your code. Puede retrasar la indización de los documentos que dieron error y volver a intentarlo; puede crear un registro y continuar, como hace el ejemplo, o puede adoptar otro enfoque según los requisitos de coherencia de datos de la aplicación.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.

Nota

Puede usar el método FindFailedActionsToRetry para construir un nuevo lote que contenga solo las acciones que dieron error en una llamada anterior a Index.You can use the FindFailedActionsToRetry method to construct a new batch containing only the actions that failed in a previous call to Index. Hay una explicación sobre cómo se usa correctamente en StackOverflow.There is a discussion of how to properly use it on StackOverflow.

Por último, el método UploadDocuments se retrasa durante dos segundos.Finally, the UploadDocuments method delays for two seconds. La indización ocurre de manera asincrónica en el servicio Azure Search, por lo que la aplicación de ejemplo debe esperar unos momentos para asegurarse de que los documentos estén disponibles para la búsqueda.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. Retrasos así solo suelen ser necesarios en las pruebas, demostraciones y aplicaciones de ejemplo.Delays like this are typically only necessary in demos, tests, and sample applications.

Gestión de documentos del SDK de .NETHow the .NET SDK handles documents

Quizás se pregunte cómo consigue el SDK de Azure para .NET cargar en el índice las instancias de una clase definida por el usuario como Hotel .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 responder mejor a esa pregunta, echemos un vistazo a la clase 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;

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

Lo primero que se debe tener en cuenta es que el nombre de cada propiedad pública de la clase Hotel se asignará a un campo con el mismo nombre en la definición del índice.The first thing to notice is that the name of each public property in the Hotel class will map to a field with the same name in the index definition. Si quiere que cada campo comience por una letra minúscula, puede indicar al SDK que asigne los nombres de propiedad en mayúscula y minúscula automáticamente con el atributo [SerializePropertyNamesAsCamelCase] de la clase.If you would like each field to start with a lower-case letter ("camel case"), you can tell the SDK to map the property names to camel-case automatically with the [SerializePropertyNamesAsCamelCase] attribute on the class. Este escenario es habitual en aplicaciones .NET que realizan enlaces de datos cuando el esquema de destino está fuera del control del desarrollador de la aplicación, sin infringir ninguna directriz de nomenclatura de "Pascal Case" en .NET.This scenario is common in .NET applications that perform data-binding where the target schema is outside the control of the application developer without having to violate the "Pascal case" naming guidelines in .NET.

Nota

El SDK de .NET para Azure Search usa la biblioteca NewtonSoft JSON.NET para serializar y deserializar los objetos de modelo personalizados en JSON y de este.The Azure Search .NET SDK uses the NewtonSoft JSON.NET library to serialize and deserialize your custom model objects to and from JSON. Puede personalizar esta serialización si es necesario.You can customize this serialization if needed. Para más información, vea Serialización personalizada con JSON.NET.For more information, see Custom Serialization with JSON.NET.

La segunda cuestión que debe considerarse es que cada propiedad se representa con atributos como IsFilterable, IsSearchable, Key y Analyzer.The second thing to notice is each property is decorated with attributes such as IsFilterable, IsSearchable, Key, and Analyzer. Estos atributos se asignan directamente a los atributos de campo correspondientes de un índice de Azure Search.These attributes map directly to the corresponding field attributes in an Azure Search index. La clase FieldBuilder usa estas propiedades para construir definiciones de campo para el índice.The FieldBuilder class uses these properties to construct field definitions for the index.

La tercera cuestión importante sobre la clase Hotel son los tipos de datos de las propiedades públicas.The third important thing about the Hotel class is the data types of the public properties. Los tipos .NET de esas propiedades se asignan a los tipos de campo equivalentes de la definición del índice.The .NET types of these properties map to their equivalent field types in the index definition. Por ejemplo, la propiedad de cadena Category se asigna al campo category, que es de tipo Edm.String.For example, the Category string property maps to the category field, which is of type Edm.String. Se dan asignaciones de tipos semejantes entre bool?, Edm.Boolean, DateTimeOffset? y Edm.DateTimeOffset, etc.There are similar type mappings between bool?, Edm.Boolean, DateTimeOffset?, and Edm.DateTimeOffset and so on. Las reglas específicas para la asignación de tipos se documentan con el método Documents.Get en la referencia del SDK de Azure Search para .NET.The specific rules for the type mapping are documented with the Documents.Get method in the Azure Search .NET SDK reference. Aunque la clase FieldBuilder se ocupa de esta asignación, todavía puede ser útil comprenderlo por si necesitara solucionar los problemas de serialización.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.

¿Se ha dado cuenta de la propiedad SmokingAllowed?Did you happen to notice the SmokingAllowed property?

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

El atributo JsonIgnore de esta propiedad indica a FieldBuilder que no la serialice en el índice como un campo.The JsonIgnore attribute on this property tells the FieldBuilder to not serialize it to the index as a field. Se trata de una excelente manera de crear propiedades calculadas del lado cliente que puede usar como aplicaciones auxiliares en su aplicación.This is a great way to create client-side calculated properties you can use as helpers in your application. En este caso, la propiedad SmokingAllowed refleja si está permitido fumar en algún objeto Room de la colección de objetos Rooms.In this case, the SmokingAllowed property reflects whether any Room in the Rooms collection allows smoking. Si todos están establecidos en false, quiere decir que no se puede fumar en todo el hotel.If all are false, it indicates that the entire hotel does not allow smoking.

Algunas propiedades, como Address y Rooms, son instancias de clases. NET.Some properties such as Address and Rooms are instances of .NET classes. Estas propiedades representan estructuras de datos más complejas y, como resultado, se requieren campos con un tipo de datos complejo en el índice.These properties represent more complex data structures and, as a result, require fields with a complex data type in the index.

La propiedad Address representa un conjunto de varios valores de la clase Address, definida aquí:The Address property represents a set of multiple values in the Address class, defined below:

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

Esta clase contiene los valores estándar que se usan para describir direcciones de Estados Unidos o Canadá.This class contains the standard values used to describe addresses in the United States or Canada. Puede usar tipos como este para agrupar campos lógicos en el índice.You can use types like this to group logical fields together in the index.

La propiedad Rooms representa una matriz de objetos Room:The Rooms property represents an array of Room objects:

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

El modelo de datos de .NET y su esquema de índice correspondiente se deben diseñar para dar cabida a la experiencia de búsqueda que quiera proporcionar al usuario final.Your data model in .NET and its corresponding index schema should be designed to support the search experience you'd like to give to your end user. Cada objeto de nivel superior en .NET (es decir, cada documento en el índice) corresponde a un resultado de búsqueda que aparecería en la interfaz de usuario.Each top level object in .NET, ie document in the index, corresponds to a search result you would present in your user interface. Por ejemplo, en una aplicación de búsqueda de alojamiento, puede que los usuarios finales quieran realizar búsquedas por nombre de hotel, por las características del hotel o por las características de una habitación determinada.For example, in a hotel search application your end users may want to search by hotel name, features of the hotel, or the characteristics of a particular room. Analizaremos algunos ejemplos de consultas un poco más adelante.We'll cover some query examples a little later.

Esta posibilidad de usar sus propias clases para interactuar con los documentos del índice funciona en ambas direcciones: también puede recuperar los resultados de la búsqueda y hacer que el SDK los deserialice automáticamente a un tipo de su elección, como veremos en la siguiente sección.This ability to use your own classes to interact with documents in the index 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.

Nota

El SDK de Azure Search para .NET también admite documentos de tipo dinámico mediante la clase Document, que es una asignación clave/valor de nombres de campo a 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. Esto es útil en escenarios en los que no se conoce el esquema del índice en el momento del diseño o en los que resulte inconveniente enlazar a clases de modelo específicas.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 los métodos del SDK que se ocupan de los documentos tienen sobrecargas que funcionan con la clase Document , así como sobrecargas de asignación rigurosa que aceptan un 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. En el código de ejemplo de este tutorial solo se utilizan las últimas.Only the latter are used in the sample code in this tutorial. La clase Document se hereda de Dictionary<string, object>.The Document class inherits from Dictionary<string, object>.

¿Por qué debería usar tipos de datos que aceptan valores null?Why you should use nullable data types

Al diseñar sus propias clases de modelo para asignar a un índice de Azure Search, es recomendable declarar las propiedades de tipos de valor como bool y int que aceptan valores NULL (por ejemplo: bool? en lugar 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). Si usa un tipo de modelo con una propiedad que no acepta valores NULL, tendrá que garantizar que ningún documento del índice contiene un valor NULL para el campo correspondiente.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. Ni el SDK ni el servicio Azure Search le permitirá aplicar esto.Neither the SDK nor the Azure Search service will help you to enforce this.

No solo se trata de una cuestión hipotética: imagine un escenario donde se agrega un nuevo campo a un índice existente que es de 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. Después de actualizar la definición del índice, todos los documentos tendrán un valor null para ese campo nuevo (ya que todos los tipos aceptan valores NULL en 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). Si después usa una clase de modelo con una propiedad int que no acepta valores NULL para ese campo, obtendrá JsonSerializationException así al intentar recuperar 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 este motivo, recomendamos utilizar tipos que aceptan valores NULL en las clases de modelo como procedimiento recomendado.For this reason, we recommend that you use nullable types in your model classes as a best practice.

Serialización personalizada con JSON.NETCustom Serialization with JSON.NET

El SDK usa JSON.NET para serializar y deserializar documentos.The SDK uses JSON.NET for serializing and deserializing documents. Puede personalizar la serialización y deserialización si lo necesita definiendo su propio JsonConverter o IContractResolver.You can customize serialization and deserialization if needed by defining your own JsonConverter or IContractResolver. Para más información, vea la documentación de JSON.NET.For more information, see the JSON.NET documentation. Esto puede ser útil cuando desea adaptar una clase de modelo existente de la aplicación para usarla con Azure Search y otros escenarios más avanzados.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 ejemplo, con la serialización personalizada, puede:For example, with custom serialization you can:

  • Incluir o excluir determinadas propiedades de la clase de modelo para que se almacenen como campos del documento.Include or exclude certain properties of your model class from being stored as document fields.
  • Asignar entre los nombres de propiedad del código y los nombres de campo del índice.Map between property names in your code and field names in your index.
  • Cree atributos personalizados que se puedan usar para asignar propiedades a campos de documento.Create custom attributes that can be used for mapping properties to document fields.

Puede encontrar ejemplos de implementación de serialización personalizada en las pruebas unitarias del SDK de .NET para Azure Search en GitHub.You can find examples of implementing custom serialization in the unit tests for the Azure Search .NET SDK on GitHub. Esta carpeta es un buen punto de partida.A good starting point is this folder. Contiene clases que las pruebas de serialización personalizada utilizan.It contains classes that are used by the custom serialization tests.

Búsqueda de documentos en el índiceSearching for documents in the index

El último paso de la aplicación de ejemplo es buscar algunos documentos en el í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 '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 ejecuta una consulta, este método crea primero un nuevo objeto SearchParameters.Each time it executes a query, this method first creates a new SearchParameters object. Este objeto se usa para especificar más opciones en la consulta, como el orden, los filtros, la paginación y las facetas.This object is used to specify additional options for the query such as sorting, filtering, paging, and faceting. En este método, vamos a establecer la propiedad Filter, Select, OrderBy y Top para diferentes consultas.In this method, we're setting the Filter, Select, OrderBy, and Top property for different queries. Todas las SearchParameterspropiedades se documentan aquí.All the SearchParameters properties are documented here.

El siguiente paso consiste en ejecutar la consulta de búsqueda.The next step is to actually execute the search query. La búsqueda se ejecuta mediante el método Documents.Search.Running the search is done using the Documents.Search method. Para cada consulta, pasamos el texto de búsqueda para usarlo como cadena (o "*" si no hay ningún texto de búsqueda), además de los parámetros de búsqueda creados 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. También especificamos Hotel como el parámetro de tipo para Documents.Search, lo que indica al SDK que deserialice los documentos de los resultados de búsqueda en objetos de 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.

Nota

Puede encontrar más información acerca de la sintaxis de expresiones de consulta de búsqueda aquí.You can find more information about the search query expression syntax here.

Por último, después de cada consulta este método recorre en iteración todas las coincidencias de los resultados de búsqueda e imprime cada documento en la consola: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();
}

Examinemos más de cerca cada una de las consultas de una en una.Let's take a closer look at each of the queries in turn. Este es el código para ejecutar la primera consulta:Here is the code to execute the first query:

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

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

WriteDocuments(results);

En este caso, buscamos en todo el índice la palabra "motel" en cualquier campo de búsqueda, y solo queremos recuperar los nombres de hotel, según lo especificado por el parámetro Select.In this case, we're searching the entire index for the word "motel" in any searchable field and we only want to retrieve the hotel names, as specified by the Select parameter. Estos son los resultados:Here are the results:

Name: Secret Point Motel

Name: Twin Dome Motel

La consulta siguiente es un poco más interesante.The next query is a little more interesting. Queremos encontrar hoteles que tengan habitaciones con una tarifa por noche inferior a 100 $, y que solo se devuelvan el identificador del hotel y la descripción:We want to find any hotels that have a room with a nightly rate of less than $100 and return only the hotel ID and description:

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 una expresión $filter de OData, Rooms/any(r: r/BaseRate lt 100), para filtrar los documentos del índice.This query uses an OData $filter expression, Rooms/any(r: r/BaseRate lt 100), to filter the documents in the index. Aquí se usa cualquier operador para aplicar "BaseRate lt 100" a todos los elementos de la colección de objetos Rooms.This uses the any operator to apply the 'BaseRate lt 100' to every item in the Rooms collection. Puede encontrar más información acerca de la sintaxis de OData admitida por Azure Search aquí.You can find out more about the OData syntax that Azure Search supports here.

Estos son los resultados de la consulta:Here are the results of the query:

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

Seguidamente, quiere encontrar los dos mejores hoteles que se han renovado más recientemente y mostrar el nombre del hotel y la última fecha de renovación.Next, we want to find the top two hotels that have been most recently renovated, and show the hotel name and last renovation date. Este es el 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);

En este caso, usamos de nuevo la sintaxis de OData para especificar el parámetro OrderBy como lastRenovationDate desc.In this case, we again use OData syntax to specify the OrderBy parameter as lastRenovationDate desc. También establecemos Top en 2 para tener la seguridad de que solo obtenemos los dos documentos principales.We also set Top to 2 to ensure we only get the top two documents. Como antes, establecemos Select para especificar los campos que se deben devolver.As before, we set Select to specify which fields should be returned.

Estos son los 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 último, queremos encontrar todos los nombres de hoteles que coincidan con la palabra "hotel":Finally, we want to find all hotels names that match the word "hotel":

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

WriteDocuments(results);

Y estos son los resultados, que incluyen todos los campos, ya que no se especificó la propiedad Select:And here are the results, which include all fields since we did not specify the Select property:

HotelId: 3
Name: Triple Landscape Hotel
...

Este paso finaliza el tutorial, pero no se detenga aquí.This step completes the tutorial, but don't stop here. **En los pasos siguientes se proporcionan recursos adicionales para obtener más información sobre Azure Search.**Next steps provide additional resources for learning more about Azure Search.

Pasos siguientesNext steps