Comment utiliser Microsoft.Azure.Search dans une application .NET C#

Cet article explique comment créer et gérer des objets de recherche à l’aide de C# et de la bibliothèque cliente héritée, Microsoft.Azure.Search (version 10) dans le Kit de développement logiciel (SDK) Azure pour .NET.

La version 10 est la dernière version du package Microsoft.Azure.Search. À l’avenir, de nouvelles fonctionnalités seront déployées dans Azure.Search.Documents de l’équipe du Kit de développement logiciel (SDK) Azure.

Remarque

Si vous avez des projets de développement en cours ou existants, vous pouvez continuer à utiliser la version 10. Pour les nouveaux projets ou pour utiliser de nouvelles fonctionnalités, vous devez passer à la nouvelle bibliothèque.

À propos de la version 10

Le Kit de développement logiciel (SDK) se compose de quelques bibliothèques clientes qui vous permettent de gérer vos index, sources de données, indexeurs et mappages de synonymes, ainsi que de charger et de gérer des documents, et d’exécuter des requêtes, sans avoir à gérer les détails de HTTP et JSON. Ces bibliothèques clientes sont toutes distribuées en tant que packages NuGet.

Le package NuGet principal est Microsoft.Azure.Search, qui est un méta-package qui inclut tous les autres packages en tant que dépendances. Utilisez ce package si vous venez de commencer ou si vous savez que votre application aura besoin de toutes les fonctionnalités de Recherche cognitive Azure.

Les autres packages NuGet dans le Kit de développement logiciel (SDK) sont les suivants :

  • Microsoft.Azure.Search.Data: utilisez ce package si vous développez une application .NET à l’aide de Recherche cognitive Azure et que vous devez uniquement interroger ou mettre à jour des documents dans vos index. Si vous devez également créer ou mettre à jour des index, des mappages de synonymes ou d’autres ressources au niveau du service, utilisez plutôt le package Microsoft.Azure.Search.
  • Microsoft.Azure.Search.Service: utilisez ce package si vous développez une automatisation dans .NET pour gérer les index recherche cognitive Azure, les mappages de synonymes, les indexeurs, les sources de données ou d’autres ressources de niveau de service. Si vous devez uniquement interroger ou mettre à jour des documents dans vos index, utilisez le package Microsoft.Azure.Search.Data à la place. Si vous avez besoin de toutes les fonctionnalités de Recherche cognitive Azure, utilisez plutôt le package Microsoft.Azure.Search.
  • Microsoft.Azure.Search.Common: types courants requis par les bibliothèques .NET Recherche cognitive Azure. Vous n’avez pas besoin d’utiliser ce package directement dans votre application. Il est destiné uniquement à être utilisé comme dépendance.

Les différentes bibliothèques clientes définissent des classes telles que Index, Fieldet Document, ainsi que des opérations telles que Indexes.Create et Documents.Search sur les classes SearchServiceClient et SearchIndexClient. Ces classes sont organisées dans les espaces de noms suivants :

Si vous souhaitez fournir des commentaires pour une prochaine mise à jour du Kit de développement logiciel (SDK), consultez notre page de commentaires ou créez un problème sur GitHub et mentionnez « Recherche cognitive Azure » dans le titre du problème.

Le SDK .NET cible la version 2019-05-06 de l’API REST Recherche cognitive Azure. Cette version inclut la prise en charge de types complexes, d’enrichissement par IA, de saisie semi-automatique et mode d’analyse JsonLines lors de l’indexation d’objets blob Azure.

Ce Kit de développement logiciel (SDK) ne prend pas en charge Opérations de gestion telles que la création et la mise à l’échelle des services de recherche et la gestion des clés API. Si vous devez gérer vos ressources de recherche à partir d’une application .NET, vous pouvez utiliser le kit de développement logiciel (SDK) de gestion .NET recherche cognitive Azure .

Mise à niveau vers la version 10

Si vous utilisez déjà une version antérieure du Kit de développement logiciel (SDK) .NET Recherche cognitive Azure et que vous souhaitez effectuer une mise à niveau vers la dernière version en disponibilité générale, cet article explique comment procéder.

Configuration requise du kit de développement logiciel (SDK)

  1. Visual Studio 2017 ou version ultérieure.
  2. Votre propre service Recherche cognitive Azure. Pour utiliser le Kit de développement logiciel (SDK), vous aurez besoin du nom de votre service et d’une ou de plusieurs clés API. Créer un service dans le portail vous aidera à suivre ces étapes.
  3. Téléchargez le Kit de développement logiciel (SDK) .NET Recherche cognitive Azure package NuGet à l’aide de « Gérer les packages NuGet » dans Visual Studio. Recherchez simplement le nom du package Microsoft.Azure.Search sur NuGet.org (ou l’un des autres noms de package ci-dessus si vous avez uniquement besoin d’un sous-ensemble de la fonctionnalité).

Le SDK .NET Recherche cognitive Azure prend en charge les applications ciblant .NET Framework 4.5.2 et versions ultérieures, ainsi que .NET Core 2.0 et versions ultérieures.

Scénarios principaux

Vous devez effectuer plusieurs opérations dans votre application de recherche. Dans ce tutoriel, nous allons aborder ces scénarios de base :

  • Création d’un index
  • Remplissage de l’index avec des documents
  • Recherche de documents à l’aide de la recherche en texte intégral et des filtres

L’exemple de code suivant illustre chacun de ces scénarios. N’hésitez pas à utiliser les extraits de code dans votre propre application.

Aperçu

L’exemple d’application que nous allons explorer crée un index nommé « hotels », le remplit avec quelques documents, puis exécute certaines requêtes de recherche. Voici le programme principal, montrant le flux global :

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

Remarque

Vous trouverez le code source complet de l’exemple d’application utilisé dans cette procédure pas à pas sur GitHub.

Nous allons parcourir cette étape par étape. Tout d’abord, nous devons créer une nouvelle SearchServiceClient. Cet objet vous permet de gérer les index. Pour en construire un, vous devez fournir votre nom de service Recherche cognitive Azure ainsi qu’une clé API d’administration. Vous pouvez entrer ces informations dans le fichier appsettings.json de l’exemple d’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;
}

Remarque

Si vous fournissez une clé incorrecte (par exemple, une clé de requête où une clé d’administration a été requise), l'SearchServiceClient lève un CloudException avec le message d’erreur « Interdit » la première fois que vous appelez une méthode d’opération sur celle-ci, telle que Indexes.Create. Si cela se produit, vérifiez notre clé API.

Les quelques lignes suivantes appellent des méthodes pour créer un index nommé « hotels », en le supprimant d’abord s’il existe déjà. Nous allons parcourir ces méthodes un peu plus tard.

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

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

Ensuite, l’index doit être rempli. Pour remplir l’index, nous aurons besoin d’un SearchIndexClient. Il existe deux façons d’en obtenir une : en la construisant ou en appelant Indexes.GetClient sur le SearchServiceClient. Nous utilisons ce dernier pour des raisons pratiques.

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

Remarque

Dans une application de recherche classique, la gestion des index et la population peuvent être gérées par un composant distinct des requêtes de recherche. Indexes.GetClient est pratique pour remplir un index, car il vous évite de fournir des SearchCredentialssupplémentaires. Pour ce faire, transmettez la clé d’administration que vous avez utilisée pour créer le SearchServiceClient vers la nouvelle SearchIndexClient. Toutefois, dans la partie de votre application qui exécute des requêtes, il est préférable de créer le SearchIndexClient directement afin de pouvoir passer une clé de requête, qui vous permet uniquement de lire des données, au lieu d’une clé d’administration. Cela est cohérent avec le principe du privilège minimum et aidera à rendre votre application plus sécurisée. Vous trouverez plus d’informations sur les clés d’administration et les clés de requête ici.

Maintenant que nous avons un SearchIndexClient, nous pouvons remplir l’index. La population de l'index est effectuée par une autre méthode que nous détaillerons ultérieurement.

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

Enfin, nous exécutons quelques requêtes de recherche et affichons les résultats. Cette fois, nous utilisons une autre SearchIndexClient:

ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(indexName, configuration);

RunQueries(indexClientForQueries);

Nous allons examiner de plus près la méthode RunQueries ultérieurement. Voici le code permettant de créer la nouvelle 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;
}

Cette fois, nous utilisons une clé de requête, car nous n’avons pas besoin d’un accès en écriture à l’index. Vous pouvez entrer ces informations dans le fichier appsettings.json de l’exemple d’application .

Si vous exécutez cette application avec un nom de service et des clés API valides, la sortie doit ressembler à cet exemple : (Une sortie de console a été remplacée par « ... » à des fins d’illustration.)


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

Le code source complet de l’application est fourni à la fin de cet article.

Ensuite, nous allons examiner de plus près chacune des méthodes appelées par Main.

Création d’un index

Après avoir créé un SearchServiceClient, Main supprime l’index « hotels » s’il existe déjà. Cette suppression est effectuée par la méthode suivante :

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

Cette méthode utilise la SearchServiceClient donnée pour vérifier si l’index existe, et le cas échéant, le supprimer.

Remarque

L’exemple de code de cet article utilise les méthodes synchrones du Kit de développement logiciel (SDK) .NET Recherche cognitive Azure pour plus de simplicité. Nous vous recommandons d’utiliser les méthodes asynchrones dans vos propres applications pour les maintenir évolutives et réactives. Par exemple, dans la méthode ci-dessus, vous pouvez utiliser ExistsAsync et DeleteAsync au lieu de Exists et de Delete.

Ensuite, Main crée un index « hotels » en appelant cette méthode :

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

Cette méthode crée un objet Index avec une liste d’objets Field qui définit le schéma du nouvel index. Chaque champ a un nom, un type de données et plusieurs attributs qui définissent son comportement de recherche. La classe FieldBuilder utilise la réflexion pour créer une liste d’objets Field pour l’index en examinant les attributs et propriétés publics de la classe de modèle Hotel donnée. Nous examinerons la classe Hotel plus en détail un peu plus tard.

Remarque

Vous pouvez toujours créer directement la liste des objets Field au lieu d’utiliser la fonctionnalité FieldBuilder, si nécessaire. Par exemple, vous ne souhaiterez peut-être pas utiliser une classe de modèle ou vous devrez peut-être utiliser une classe de modèle existante que vous ne souhaitez pas modifier en ajoutant des attributs.

Outre les champs, vous pouvez également ajouter des profils de scoring, des suggesteurs ou des options CORS à l’index (ces paramètres sont omis à partir de l’exemple pour la concision). Vous trouverez plus d’informations sur l’objet Index et ses parties constituantes dans l'de référence du KIT de développement logiciel (SDK), ainsi que dans la référence de l’API REST recherche cognitive Azure .

Remplissage de l’index

L’étape suivante de Main remplit l’index nouvellement créé. Cette population d’index est effectuée dans la méthode suivante : (un code est remplacé par « ... » à des fins d’illustration. Consultez l’exemple de solution complet pour le code de remplissage de données complet.)

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

Cette méthode présente quatre parties. Le premier crée un tableau de 3 objets Hotel chacun avec 3 objets Room qui serviront de données d’entrée à charger dans l’index. Ces données sont codées en dur pour plus de simplicité. Dans votre propre application, vos données proviennent probablement d’une source de données externe telle qu’une base de données SQL.

La deuxième partie crée un IndexBatch contenant les documents. Vous spécifiez l’opération que vous souhaitez appliquer au lot au moment de sa création, dans ce cas en appelant IndexBatch.Upload. Le lot est ensuite chargé dans l’index Recherche cognitive Azure par la méthode Documents.Index.

Remarque

Dans cet exemple, nous allons simplement charger des documents. Si vous souhaitez fusionner les modifications dans les documents existants ou supprimer des documents, vous pouvez créer des lots en appelant IndexBatch.Merge, IndexBatch.MergeOrUpload ou IndexBatch.Delete à la place. Vous pouvez également combiner différentes opérations dans un lot unique en appelant IndexBatch.New, qui prend une collection d’objets IndexAction, chacun indiquant à Recherche cognitive Azure d’effectuer une opération particulière sur un document. Vous pouvez créer chaque IndexAction avec sa propre opération en appelant la méthode correspondante comme IndexAction.Merge, IndexAction.Upload, et ainsi de suite.

La troisième partie de cette méthode est un bloc catch qui gère un cas d'erreur important pour l'indexation. Si votre service Recherche cognitive Azure ne parvient pas à indexer certains documents dans le lot, une IndexBatchException est générée par Documents.Index. Cette exception peut se produire si vous indexez des documents pendant que votre service est sous une charge importante. Nous vous recommandons vivement de prendre en charge explicitement ce cas de figure dans votre code. Vous pouvez retarder puis relancer l'indexation des documents qui ont échoué, ouvrir une session et continuer comme dans l’exemple, ou faire autre chose selon la cohérence des données requise par votre application.

Remarque

Vous pouvez utiliser la méthode FindFailedActionsToRetry pour construire un nouveau lot contenant uniquement les actions ayant échoué dans un appel précédent à Index. Il existe une discussion sur la façon de l’utiliser correctement sur StackOverflow.

Enfin, la méthode UploadDocuments retarde son exécution de deux secondes. L’indexation se produit de façon asynchrone dans votre service Recherche cognitive Azure. Par conséquent, l’exemple d’application doit attendre un court délai pour vous assurer que les documents sont disponibles pour la recherche. Ce genre de retard n’est nécessaire que dans les démonstrations, les tests et les exemples d'applications.

Comment le Kit de développement logiciel (SDK) .NET gère les documents

Vous vous demandez peut-être comment le Kit de développement logiciel (SDK) .NET Recherche cognitive Azure est en mesure de charger des instances d’une classe définie par l’utilisateur, comme Hotel à l’index. Pour répondre à cette question, examinons la classe Hotel :

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

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

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

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

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

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

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

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

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

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

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

    public Address Address { get; set; }

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

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

La première chose à noter est que le nom de chaque propriété publique de la classe Hotel est mappé à un champ portant le même nom dans la définition d’index. Si vous souhaitez que chaque champ commence par une lettre minuscule (« camel case »), vous pouvez demander au SDK de convertir automatiquement les noms de propriétés en camel case avec l'attribut [SerializePropertyNamesAsCamelCase] de la classe. Ce scénario est courant dans les applications .NET qui effectuent une liaison de données où le schéma cible est en dehors du contrôle du développeur d’applications sans avoir à violer les instructions d’affectation de noms « Cas Pascal » dans .NET.

Remarque

Le Kit de développement logiciel (SDK) .NET Recherche cognitive Azure utilise la bibliothèque NewtonSoft JSON.NET pour sérialiser et désérialiser vos objets de modèle personnalisés vers et depuis JSON. Vous pouvez personnaliser cette sérialisation si nécessaire. Pour plus d’informations, consultez Sérialisation Personnalisée avec JSON.NET.

La deuxième chose à remarquer est que chaque propriété est décorée avec des attributs tels que IsFilterable, IsSearchable, Keyet Analyzer. Ces attributs sont directement associés aux attributs de champ correspondants dans un index Azure Cognitive Search. La classe FieldBuilder utilise ces propriétés pour construire des définitions de champ pour l’index.

La troisième chose importante concernant la classe Hotel est les types de données des propriétés publiques. Les types .NET de ces propriétés correspondent à leurs types de champs équivalents dans la définition d’index. Par exemple, la propriété de chaîne Category correspond au champ category, qui est de type Edm.String. Il existe des mappages de type similaire entre bool?, Edm.Boolean, DateTimeOffset? et Edm.DateTimeOffset, etc. Les règles spécifiques pour le mappage de type sont documentées avec la méthode Documents.Get dans la référence du SDK .NET Azure Cognitive Search . La classe FieldBuilder s’occupe de ce mappage pour vous, mais il peut toujours être utile de comprendre si vous avez besoin de résoudre les problèmes de sérialisation.

Avez-vous remarqué la propriété SmokingAllowed ?

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

L’attribut JsonIgnore de cette propriété indique au FieldBuilder de ne pas la sérialiser sur l’index en tant que champ. Il s’agit d’un excellent moyen de créer des propriétés calculées côté client que vous pouvez utiliser en tant qu’assistance dans votre application. Dans ce cas, la propriété SmokingAllowed indique s’il est permis de fumer dans l’une des Room de la collection Rooms. Si tous sont faux, cela indique que l’hôtel entier ne permet pas de fumer.

Certaines propriétés telles que Address et Rooms sont des instances de classes .NET. Ces propriétés représentent des structures de données plus complexes et, par conséquent, nécessitent des champs avec un type de données complexe dans l’index.

La propriété Address représente un ensemble de valeurs multiples dans la classe Address, définie ci-dessous :

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

Cette classe contient les valeurs standard utilisées pour décrire les adresses aux États-Unis ou au Canada. Vous pouvez utiliser des types comme celui-ci pour regrouper les champs logiques dans l’index.

La propriété Rooms représente un tableau d’objets Room :

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

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

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

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

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

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

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

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

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

Votre modèle de données dans .NET et son schéma d’index correspondant doivent être conçus pour prendre en charge l’expérience de recherche que vous souhaitez donner à votre utilisateur final. Chaque objet de niveau supérieur dans .NET, document internet dans l’index, correspond à un résultat de recherche que vous présenteriez dans votre interface utilisateur. Par exemple, dans une application de recherche d’hôtel, vos utilisateurs finaux peuvent vouloir rechercher par nom d’hôtel, fonctionnalités de l’hôtel ou les caractéristiques d’une chambre particulière. Nous aborderons quelques exemples de requêtes un peu plus tard.

Cette possibilité d’utiliser vos propres classes pour interagir avec des documents dans l’index fonctionne dans les deux sens ; Vous pouvez également récupérer les résultats de recherche et les désérialiser automatiquement dans un type de votre choix, comme nous le verrons dans la section suivante.

Remarque

Le Kit de développement logiciel (SDK) .NET Recherche cognitive Azure prend également en charge les documents typés dynamiquement à l’aide de la classe Document, qui est un mappage clé/valeur des noms de champs aux valeurs de champ. Cela est utile dans les scénarios où vous ne connaissez pas le schéma d’index au moment du design ou où il serait inconvenient de lier à des classes de modèle spécifiques. Toutes les méthodes du Kit de développement logiciel (SDK) qui traitent des documents ont des surcharges qui fonctionnent avec la classe Document, ainsi que des surcharges fortement typées qui prennent un paramètre de type générique. Seuls les derniers sont utilisés dans l’exemple de code de ce didacticiel. La classe Document hérite de Dictionary<string, object>.

Pourquoi utiliser des types de données nullables

Lors de la conception de vos propres classes de modèle à mapper à un index Recherche cognitive Azure, nous vous recommandons de déclarer des propriétés de types valeur telles que bool et int être nullables (par exemple, bool? au lieu de bool). Si vous utilisez une propriété non nullable, vous devez garantir qu’aucun document de votre index ne contient une valeur Null pour le champ correspondant. Ni le Kit de développement logiciel (SDK) ni le service Recherche cognitive Azure ne vous aideront à l’appliquer.

Ce n’est pas seulement une préoccupation hypothétique : imaginez un scénario où vous ajoutez un nouveau champ à un index existant de type Edm.Int32. Après la mise à jour de la définition d’index, tous les documents auront une valeur Null pour ce nouveau champ (étant donné que tous les types sont nullables dans Recherche cognitive Azure). Si vous utilisez ensuite une classe de modèle avec une propriété int non nullable pour ce champ, vous obtiendrez un JsonSerializationException comme suit lors de la tentative de récupération de documents :

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

Pour cette raison, nous vous recommandons d’utiliser des types nullables dans vos classes de modèle comme meilleure pratique.

Sérialisation personnalisée avec JSON.NET

Le Kit de développement logiciel (SDK) utilise JSON.NET pour sérialiser et désérialiser des documents. Vous pouvez personnaliser la sérialisation et la désérialisation si nécessaire en définissant votre propre JsonConverter ou IContractResolver. Pour plus d’informations, consultez la documentation JSON.NET. Cela peut être utile lorsque vous souhaitez adapter une classe de modèle existante à partir de votre application pour une utilisation avec Recherche cognitive Azure et d’autres scénarios plus avancés. Par exemple, avec la sérialisation personnalisée, vous pouvez :

  • Incluez ou excluez certaines propriétés de votre classe de modèle d’être stockées en tant que champs de document.
  • Établissez une correspondance entre les noms de propriétés dans votre code et les noms de champs dans votre index.
  • Créez des attributs personnalisés qui peuvent être utilisés pour mapper des propriétés à des champs de document.

Vous trouverez des exemples d’implémentation de la sérialisation personnalisée dans les tests unitaires du Kit de développement logiciel (SDK) .NET Recherche cognitive Azure sur GitHub. Un bon point de départ est ce dossier. Il contient des classes utilisées par les tests de sérialisation personnalisés.

Recherche de documents dans l’index

La dernière étape de l’exemple d’application consiste à rechercher des documents dans l’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);
}

Chaque fois qu’elle exécute une requête, cette méthode crée d’abord un nouvel objet SearchParameters. Cet objet permet de spécifier des options supplémentaires pour la requête, comme le tri, le filtrage, la pagination et la génération de facettes. Dans cette méthode, nous définissons la Filter, Select, OrderByet Top propriété pour différentes requêtes. Toutes les propriétés SearchParameters sont documentées ici.

L’étape suivante consiste à exécuter réellement la requête de recherche. La recherche est exécutée à l’aide de la méthode Documents.Search. Pour chaque requête, nous transmettons le texte de recherche à utiliser comme chaîne (ou "*" s’il n’y a pas de texte de recherche), ainsi que les paramètres de recherche créés précédemment. Nous spécifions également Hotel comme paramètre de type pour Documents.Search, qui demande au SDK de désérialiser les documents figurant dans les résultats de recherche, en objets de type Hotel.

Remarque

Vous trouverez plus d’informations sur la syntaxe d’expression de requête de recherche ici.

Enfin, après chaque requête, cette méthode itère toutes les correspondances dans les résultats de la recherche, en imprimant chaque document dans la console :

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

    Console.WriteLine();
}

Examinons de plus près chacune des requêtes à tour de rôle. Voici le code à exécuter la première requête :

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

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

WriteDocuments(results);

Dans ce cas, nous recherchons l’index entier pour le mot « motel » dans n’importe quel champ pouvant faire l’objet d’une recherche et nous voulons uniquement récupérer les noms d’hôtel, comme spécifié par le paramètre Select. Voici les résultats :

Name: Secret Point Motel

Name: Twin Dome Motel

La requête suivante est un peu plus intéressante. Nous voulons trouver n'importe quels hôtels qui ont une chambre avec un tarif nocturne de moins de 100 $ et fournir uniquement l’ID et la description de l’hôtel.

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

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

WriteDocuments(results);

Cette requête utilise une expression $filter OData (Rooms/any(r: r/BaseRate lt 100)) pour filtrer les documents dans l’index. Cela utilise l'opérateur quelconque pour appliquer « BaseRate lt 100 » à chaque élément de la collection Rooms. Vous trouverez plus d’informations sur la syntaxe OData prise en charge par Recherche cognitive Azure ici.

Voici les résultats de la requête :

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

Ensuite, nous voulons trouver les deux meilleurs hôtels qui ont été récemment rénovés, et montrer le nom et la date de rénovation de l’hôtel. Voici le code :

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

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

WriteDocuments(results);

Dans ce cas, nous utilisons à nouveau la syntaxe OData pour spécifier le paramètre OrderBy comme lastRenovationDate desc. Nous avons également défini Top sur 2 pour nous assurer que nous obtenons uniquement les deux premiers documents. Comme précédemment, nous définissons Select pour spécifier les champs à retourner.

Voici les résultats :

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

Enfin, nous voulons trouver tous les noms d’hôtels qui correspondent au mot « hôtel » :

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

WriteDocuments(results);

Voici les résultats, qui incluent tous les champs, car nous n’avons pas spécifié la propriété Select :

	HotelId: 3
	Name: Triple Landscape Hotel
	...

Cette étape termine le tutoriel, mais ne vous arrêtez pas ici. **Les étapes suivantes fournissent des ressources supplémentaires pour en savoir plus sur Recherche cognitive Azure.

Étapes suivantes