Share via


Come usare Microsoft.Azure.Search in un'applicazione .NET C#

Questo articolo illustra come creare e gestire oggetti di ricerca usando C# e la libreria client legacy, Microsoft.Azure.Search (versione 10) in Azure SDK per .NET.

La versione 10 è l'ultima versione del pacchetto Microsoft.Azure.Search. In futuro verranno implementate nuove funzionalità nel team di Azure.Search.Documents del team di Azure SDK.

Nota

Se si hanno progetti di sviluppo esistenti o in volo, è possibile continuare a usare la versione 10. Per i nuovi progetti o per usare nuove funzionalità, è necessario passare alla nuova libreria.

Informazioni sulla versione 10

Questo SDK è costituito da alcune librerie client che consentono di gestire indici, origini dati, indicizzatori e mappe di sinonimi, nonché di caricare e gestire documenti ed eseguire query, senza doversi occupare dei dettagli di HTTP e JSON. Tutte queste librerie client vengono distribuite come pacchetti NuGet.

Il pacchetto NuGet principale è Microsoft.Azure.Search, ovvero il meta-pacchetto che include tutti gli altri pacchetti come dipendenze. Usare questo pacchetto se si sta appena iniziando o se si sa che l'applicazione richiederà tutte le funzionalità di Ricerca cognitiva di Azure.

Gli altri pacchetti NuGet in SDK sono:

  • Microsoft.Azure.Search.Data: usare questo pacchetto se si sta sviluppando un'applicazione .NET usando Ricerca cognitiva di Azure e è necessario eseguire query o aggiornare i documenti negli indici. Se invece occorre anche creare o aggiornare gli indici, le mappe di sinonimi o altre risorse a livello di servizio, usare il pacchetto Microsoft.Azure.Search.
  • Microsoft.Azure.Search.Service: usare questo pacchetto se si sta sviluppando l'automazione in .NET per gestire gli indici Ricerca cognitiva di Azure, mappe sinonimi, indicizzatori, origini dati o altre risorse a livello di servizio. Se invece occorre solo eseguire query o aggiornare i documenti negli indici, usare il pacchetto Microsoft.Azure.Search.Data. Se sono necessarie tutte le funzionalità di Ricerca cognitiva di Azure, usare invece il Microsoft.Azure.Search pacchetto.
  • Microsoft.Azure.Search.Common: tipi comuni necessari per le librerie .NET Ricerca cognitiva di Azure. Non è necessario usare questo pacchetto direttamente nell'applicazione. È destinato solo a essere usato come dipendenza.

Le diverse librerie client definiscono classi come Index, Field, e Document, nonché operazioni quali Indexes.Create e Documents.Search sulle classi SearchServiceClient e SearchIndexClient. Le classi sono organizzate negli spazi dei nomi seguenti:

Se si desidera fornire commenti e suggerimenti per un aggiornamento futuro dell'SDK, vedere la pagina di feedback o creare un problema in GitHub e indicare "Ricerca cognitiva di Azure" nel titolo del problema.

La versione di .NET SDK è destinata all'API REST Ricerca cognitiva di Azure.2019-05-06 Questa versione include il supporto per tipi complessi, arricchimento di intelligenza artificiale, completamento automatico e modalità di analisi JsonLines durante l'indicizzazione dei BLOB di Azure.

Questo SDK non supporta operazioni di gestione, come la creazione e la scalabilità di servizi di ricerca e la gestione delle chiavi API. Se è necessario gestire le risorse di ricerca da un'applicazione .NET, è possibile usare .NET Management SDK Ricerca cognitiva di Azure.NET.

Eseguire l'aggiornamento alla versione 10

Se si usa già una versione precedente del Ricerca cognitiva di Azure .NET SDK e si vuole eseguire l'aggiornamento alla versione più recente disponibile a livello generale, questo articolo illustra come.

Requisiti di SDK

  1. Visual Studio 2017 o versioni successive.
  2. Servizio di Ricerca cognitiva di Azure personalizzato. Per utilizzare l'SDK, è necessario il nome del servizio e una o più chiavi API. Creare un servizio nel portale per eseguire facilmente questi passaggi.
  3. Scaricare il pacchetto Ricerca cognitiva di Azure .NET SDK NuGet usando "Gestisci pacchetti NuGet" in Visual Studio. Cercare semplicemente il nome del pacchetto Microsoft.Azure.Search in NuGet.org (o uno degli altri nomi dei pacchetti riportati sopra se è necessario solo un subset delle funzionalità).

Il Ricerca cognitiva di Azure .NET SDK supporta applicazioni destinate a .NET Framework 4.5.2 e versioni successive, oltre a .NET Core 2.0 e versioni successive.

Scenari chiave

Esistono diverse operazioni che è necessario eseguire nell'applicazione di ricerca. In questa esercitazione saranno illustrati questi scenari chiave:

  • Creazione di un indice
  • Popolamento dell’indice con i documenti
  • Ricerca di documenti mediante filtri e ricerca con testo completo

Il codice di esempio seguente illustra ognuno di questi scenari. È possibile utilizzare i frammenti di codice nell'applicazione.

Panoramica

L'applicazione di esempio crea un nuovo indice denominato "hotels", vi inserisce alcuni documenti, quindi esegue alcune query di ricerca. Ecco il programma principale che mostra il flusso generale:

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

Il codice sorgente completo dell'applicazione di esempio utilizzata è disponibile in questa procedura dettagliata su GitHub.

La procedura verrà illustrata passo per passo. In primo luogo è necessario creare un nuovo SearchServiceClient. Questo oggetto consente di gestire gli indici. Per costruire uno, è necessario specificare il nome del servizio Ricerca cognitiva di Azure e una chiave API di amministrazione. È possibile immettere queste informazioni nel file appsettings.json dell'applicazione di esempio.

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

Se si fornisce una chiave non corretta (ad esempio una chiave di query dove era richiesta una chiave di amministrazione), SearchServiceClient invierà un CloudException con il messaggio di errore "Non consentito" la prima volta che si chiama un metodo di operazione, ad esempio Indexes.Create. In questo caso, eseguire una doppia verifica della chiave API.

Le righe successive chiamano i metodi per creare un indice denominato "hotels", eliminandolo prima se esiste già. Tali metodi saranno illustrati più avanti.

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

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

Successivamente, l'indice deve essere popolato. Per popolare l'indice, sarà necessario un SearchIndexClientoggetto . Esistono due modi per ottenere uno: creandolo oppure chiamando Indexes.GetClient sul SearchServiceClient. Per comodità, sarà utilizzato il secondo.

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

Nota

In un'applicazione di ricerca tipica, la gestione degli indici e la popolazione possono essere gestite da un componente separato dalle query di ricerca. Indexes.GetClient è pratico per popolare un indice perché consente di risparmiare i problemi di fornire ulteriori SearchCredentials. Questa operazione viene eseguita passando la chiave di amministrazione utilizzata per creare il SearchServiceClient al nuovo SearchIndexClient. Tuttavia, nella parte dell'applicazione che esegue query, è preferibile creare direttamente SearchIndexClient in modo che sia possibile passare una chiave di query, che consente di leggere solo i dati anziché una chiave di amministrazione. Questo procedimento è coerente con il principio del privilegio minimo e consente di rendere più sicura l'applicazione. È possibile trovare ulteriori informazioni su chiavi di amministrazione e chiavi di query qui.

Ora che è disponibile un SearchIndexClient, è possibile popolare l'indice. La popolazione di indici viene eseguita da un altro metodo che verrà illustrato più avanti.

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

Infine, saranno eseguite alcune query di ricerca e visualizzati i risultati. In questo caso viene utilizzato un SearchIndexClient diverso:

ISearchIndexClient indexClientForQueries = CreateSearchIndexClient(indexName, configuration);

RunQueries(indexClientForQueries);

Esamineremo il metodo RunQueries più da vicino in un secondo momento. Ecco il codice per creare il nuovo 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;
}

In questo caso è utilizzata una chiave di query poiché non è necessario l'accesso in scrittura all'indice. È possibile immettere queste informazioni nel file appsettings.json dell'applicazione di esempio.

Se si esegue questa applicazione con un nome di servizio e chiavi API valide, l'output dovrebbe essere simile al seguente: (Alcuni output della console sono stati sostituiti con "..." per scopi di illustrazione.


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

Alla fine di questo articolo viene fornito il codice sorgente completo dell'applicazione.

Successivamente, verrà esaminato ognuno dei metodi chiamati da Main.

Creazione di un indice

Dopo aver creato un SearchServiceClientoggetto , Main elimina l'indice "hotels" se esiste già. Tale eliminazione viene eseguita dal metodo seguente:

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

Questo metodo utilizza il SearchServiceClient fornito per verificare l'esistenza dell'indice ed eventualmente eliminarlo.

Nota

Il codice di esempio in questo articolo usa i metodi sincroni dell'SDK Ricerca cognitiva di Azure .NET per semplicità. È consigliabile utilizzare i metodi asincroni nelle proprie applicazioni per mantenerle scalabili e reattive. Ad esempio, nel metodo precedente è possibile utilizzare ExistsAsync e DeleteAsync anziché Exists e Delete.

Successivamente, Main crea un nuovo indice "hotel" chiamando questo metodo:

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

Questo metodo crea un nuovo oggetto Index con un elenco di oggetti Field che definisce lo schema del nuovo indice. Ogni campo ha un nome, un tipo di dati e diversi attributi che definiscono il comportamento della ricerca. La classe FieldBuilder utilizza la reflection per creare un elenco di oggetti Field per l'indice esaminando le proprietà pubbliche e gli attributi della classe modello Hotel specificata. Esamineremo la classe Hotel più da vicino in un secondo momento.

Nota

Se necessario, è inoltre possibile creare l'elenco di oggetti Field direttamente anziché utilizzando FieldBuilder. Ad esempio, è possibile evitare di utilizzare una classe modello o potrebbe essere necessario utilizzare una classe modello esistente che non si desidera modificare aggiungendo attributi.

Oltre ai campi, è anche possibile aggiungere profili di punteggio, suggerisci o opzioni CORS all'indice (questi parametri vengono omessi dall'esempio per brevità). È possibile trovare altre informazioni sull'oggetto Index e sulle relative parti costitutive nel riferimento dell'SDK, nonché nel riferimento all'API REST Ricerca cognitiva di Azure.

Popolamento dell'indice

Il passaggio successivo in Main popola l'indice appena creato. Questa popolazione di indici viene eseguita nel metodo seguente: (Alcuni codici sostituiti con "..." per scopi di illustrazione. Vedere la soluzione di esempio completa per il codice completo del popolamento dei dati.

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

Questo metodo è costituito da quattro parti. La prima crea una matrice di 3 oggetti ognuno con 3 HotelRoom oggetti che fungeranno da dati di input da caricare nell'indice. Questi dati sono hardcoded per motivi di semplicità. Nell'applicazione, i dati probabilmente proverranno da un'origine dati esterna, ad esempio un database SQL.

La seconda parte crea un oggetto IndexBatch contenente i documenti. Specificare l'operazione che si vuole applicare al batch al momento della creazione, in questo caso chiamando IndexBatch.Upload. Il batch viene quindi caricato nell'indice Ricerca cognitiva di Azure dal Documents.Index metodo .

Nota

In questo esempio, verranno semplicemente caricati i documenti. Se invece si vogliono unire le modifiche in documenti esistenti o eliminare documenti, creare batch chiamando IndexBatch.Merge, IndexBatch.MergeOrUpload o IndexBatch.Delete. È anche possibile combinare operazioni diverse in un singolo batch chiamando IndexBatch.New, che accetta una raccolta di IndexAction oggetti, ognuno dei quali indica Ricerca cognitiva di Azure di eseguire un'operazione specifica in un documento. È possibile creare ogni oggetto IndexAction con la propria operazione chiamando il metodo corrispondente, ad esempio IndexAction.Merge, IndexAction.Upload e così via.

La terza parte di questo metodo è un blocco catch che gestisce un caso di errore importante per l'indicizzazione. Se il servizio di Ricerca cognitiva di Azure non riesce a indicizzare alcuni dei documenti nel batch, IndexBatchException viene generato da Documents.Index. Questa eccezione può verificarsi se si stanno indicizzando documenti mentre il servizio è in carico elevato. Si consiglia di gestire in modo esplicito questo caso nel codice. È possibile ritardare e quindi ritentare l'indicizzazione di documenti, accedere e continuare come nell'esempio, oppure eseguire altre attività a seconda dei requisiti di coerenza di dati dell'applicazione.

Nota

È possibile utilizzare il metodo FindFailedActionsToRetry per costruire un nuovo batch contenente solo le azioni non riuscite in una precedente chiamata a Index. È disponibile una discussione su come usare questo metodo in modo corretto su StackOverflow.

Infine, il metodo UploadDocuments ritarda per due secondi. L'indicizzazione avviene in modo asincrono nel servizio Ricerca cognitiva di Azure, quindi l'applicazione di esempio deve attendere un breve periodo di tempo per assicurarsi che i documenti siano disponibili per la ricerca. Ritardi come questi in genere sono necessari solo in applicazioni di esempio, test e demo.

Modalità di gestione dei documenti in .NET SDK

Si potrebbe chiedersi come Ricerca cognitiva di Azure .NET SDK è in grado di caricare istanze di una classe definita dall'utente come Hotel nell'indice. è possibile esaminare 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 prima cosa da notare è che il nome di ogni proprietà pubblica nella Hotel classe eseguirà il mapping a un campo con lo stesso nome nella definizione di indice. Se si desidera che ogni campo inizi con una lettera minuscola ("camel case"), è possibile indicare all'SDK di eseguire il mapping dei nomi delle proprietà a camel-case automaticamente con l'attributo [SerializePropertyNamesAsCamelCase] nella classe. Questo scenario è comune nelle applicazioni .NET che eseguono il data binding in cui lo schema di destinazione è esterno al controllo dello sviluppatore dell'applicazione senza dover violare le linee guida di denominazione "Pascal case" in .NET.

Nota

L'Ricerca cognitiva di Azure .NET SDK usa la libreria NewtonSoft JSON.NET per serializzare e deserializzare gli oggetti modello personalizzati da e verso JSON. Se necessario, è possibile personalizzare questa serializzazione. Per altre informazioni, vedere Serializzazione personalizzata con JSON.NET.

La seconda cosa da notare è ogni proprietà è decorata con attributi come IsFilterable, , IsSearchableKeye Analyzer. Questi attributi vengono mappati direttamente agli attributi di campo corrispondenti in un indice Ricerca cognitiva di Azure. La FieldBuilder classe usa queste proprietà per costruire definizioni di campo per l'indice.

La terza cosa importante sulla Hotel classe è i tipi di dati delle proprietà pubbliche. Viene eseguito il mapping dei tipi .NET di queste proprietà ai tipi di campi equivalenti nella definizione dell'indice. Ad esempio, viene eseguito il mapping della proprietà stringa Category al campo category, che è di tipo Edm.String. Esistono mapping di tipi simili tra bool?, Edm.Boolean, DateTimeOffset?e Edm.DateTimeOffset così via. Le regole specifiche per il mapping dei tipi sono documentate con il Documents.Get metodo nel riferimento Ricerca cognitiva di Azure .NET SDK. La classe FieldBuilder si occupa di questo mapping automaticamente, ma la comprensione può comunque essere utile nel caso in cui sia necessario risolvere eventuali problemi di serializzazione.

Si è notato la SmokingAllowed proprietà?

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

L'attributo JsonIgnore in questa proprietà indica all'utente FieldBuilder di non serializzarlo nell'indice come campo. Questo è un ottimo modo per creare proprietà calcolate sul lato client che è possibile usare come helper nell'applicazione. In questo caso, la SmokingAllowed proprietà riflette se qualsiasi Room nella Rooms raccolta consente di fumare. Se tutto è falso, indica che l'intero hotel non consente il fumo.

Alcune proprietà, Address ad esempio e Rooms sono istanze di classi .NET. Queste proprietà rappresentano strutture di dati più complesse e, di conseguenza, richiedono campi con un tipo di dati complesso nell'indice.

La Address proprietà rappresenta un set di più valori nella Address classe, definito di seguito:

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

Questa classe contiene i valori standard usati per descrivere gli indirizzi nell'Stati Uniti o nel Canada. È possibile usare tipi come questo per raggruppare i campi logici insieme nell'indice.

La Rooms proprietà rappresenta una matrice di Room oggetti:

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

Il modello di dati in .NET e il relativo schema di indice corrispondente devono essere progettati per supportare l'esperienza di ricerca da fornire all'utente finale. Ogni oggetto di primo livello in .NET, ie document nell'indice, corrisponde a un risultato di ricerca presente nell'interfaccia utente. Ad esempio, in un'applicazione di ricerca hotel gli utenti finali possono voler cercare in base al nome dell'hotel, alle caratteristiche dell'hotel o alle caratteristiche di una determinata stanza. Verranno illustrati alcuni esempi di query un po' più avanti.

Questa possibilità di usare le proprie classi per interagire con i documenti nell'indice funziona in entrambe le direzioni; È anche possibile recuperare i risultati della ricerca e avere automaticamente l'SDK deserializzarli in un tipo di propria scelta, come si vedrà nella sezione successiva.

Nota

L'Ricerca cognitiva di Azure .NET SDK supporta anche documenti tipizzati dinamicamente usando la Document classe, ovvero un mapping chiave/valore dei nomi dei campi ai valori dei campi. Ciò è utile negli scenari in cui non si conosce lo schema di indice in fase di progettazione o quando l’associazione a classi di modello specifico non sarebbe conveniente. Tutti i metodi in SDK che gestiscono documenti dispongono di overload che funzionano con la classe Document , nonché overload fortemente tipizzati che accettano un parametro di tipo generico. Solo questi ultimi vengono utilizzati nell'esempio di codice fornito in questa esercitazione. La Document classe eredita da Dictionary<string, object>.

Perché usare tipi di dati nullable

Quando si progettano classi di modello personalizzate per eseguire il mapping a un indice Ricerca cognitiva di Azure, è consigliabile dichiarare proprietà di tipi bool di valore come e int per essere nullable (ad esempio, bool? anziché bool). Se si usa una proprietà che non ammette i valori Null, è necessario garantire che nessun documento nell'indice contenga un valore Null per il campo corrispondente. Né l'SDK né il servizio Ricerca cognitiva di Azure consentono di applicare questa operazione.

Non è solo un problema ipotetico: si pensi a uno scenario in cui si aggiunge un nuovo campo a un indice esistente di tipo Edm.Int32. Dopo aver aggiornato la definizione dell'indice, tutti i documenti avranno un valore Null per tale nuovo campo (poiché tutti i tipi sono nullable in Ricerca cognitiva di Azure). Se quindi si usa una classe di modelli con una proprietà int che non ammette i valori Null per tale campo, verrà restituita un'eccezione JsonSerializationException, come questa, quando si cercherà di recuperare i documenti:

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

Per questo motivo, è consigliabile usare tipi nullable nelle classi di modelli.

Serializzazione personalizzata con JSON.NET

L'SDK usa JSON.NET per serializzare e deserializzare i documenti. È possibile personalizzare la serializzazione e la deserializzazione se necessario definendo la propria JsonConverter o IContractResolver. Per altre informazioni, vedere la documentazione di JSON.NET. Questo può essere utile quando si vuole adattare una classe di modello esistente dall'applicazione da usare con Ricerca cognitiva di Azure e altri scenari più avanzati. Con la serializzazione personalizzata, ad esempio, è possibile:

  • Includere o escludere determinate proprietà della classe modello da archiviare come campi di un documento.
  • Eseguire il mapping tra i nomi di proprietà nel codice e i nomi di campo nell'indice.
  • Creare gli attributi personalizzati che possono essere utilizzati per il mapping di proprietà ai campi di documento.

È possibile trovare esempi di implementazione della serializzazione personalizzata negli unit test per Ricerca cognitiva di Azure .NET SDK in GitHub. È consigliabile iniziare da questa cartella, che contiene le classi usate dai test di serializzazione personalizzata.

Ricerca di documenti nell'indice

L'ultimo passaggio dell'applicazione di esempio consiste nel cercare alcuni documenti nell'indice:

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

Ogni volta che viene eseguita una query, questo metodo crea prima un nuovo oggetto SearchParameters. Questo oggetto viene usato per specificare opzioni aggiuntive per la query, ad esempio l'ordinamento, il filtro, il paging e il faceting. In questo metodo, impostiamo le proprietà Filter, Select, OrderBy e Top per diverse query. Tutte le proprietà SearchParameters sono documentate qui.

Il passaggio successivo consiste nell’esecuzione effettiva della query di ricerca. L'esecuzione della ricerca viene eseguita usando il Documents.Search metodo . Per ogni query, è possibile passare il testo di ricerca da utilizzare come stringa (o "*" se non è presente il testo di ricerca), oltre ai parametri di ricerca creati in precedenza. Viene inoltre specificato Hotel come parametro di tipo per Documents.Search, che indica all'SDK di deserializzare i documenti nei risultati della ricerca in oggetti di tipo Hotel.

Nota

È possibile trovare ulteriori informazioni sulla sintassi delle espressioni di query di ricerca qui.

Infine, dopo ogni query questo metodo scorre tutte le corrispondenze nei risultati della ricerca, stampando tutti i documenti nella console:

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

    Console.WriteLine();
}

Queste query saranno ora esaminate in maggiore dettaglio. Ecco il codice per eseguire la prima query:

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

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

WriteDocuments(results);

In questo caso, si sta cercando l'intero indice per la parola "motel" in qualsiasi campo ricercabile e si vuole recuperare solo i nomi degli hotel, come specificato dal Select parametro. Ecco i risultati:

Name: Secret Point Motel

Name: Twin Dome Motel

La query successiva è un po' più interessante. Vogliamo trovare qualsiasi hotel con una camera con una tariffa notturna inferiore a $100 e restituire solo l'ID e la descrizione dell'hotel:

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

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

WriteDocuments(results);

Questa query utilizza un'espressione $filter OData, Rooms/any(r: r/BaseRate lt 100), per filtrare i documenti nell'indice. In questo modo viene usato l'operatore per applicare il valore "BaseRate lt 100" a ogni elemento dell'insieme Rooms. Per altre informazioni sulla sintassi OData supportata da Ricerca cognitiva di Azure, vedere qui.

Di seguito sono riportati i risultati della 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...

Ora, si desidera individuare i due hotel ristrutturati più di recente e visualizzare il nome dell'hotel e la data dell'ultima ristrutturazione. Il codice è il seguente:

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

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

WriteDocuments(results);

In questo caso, viene nuovamente utilizzata la sintassi di OData per specificare il parametro OrderBy come lastRenovationDate desc. È inoltre possibile impostare Top su 2 per avere la certezza che la ricerca restituisca solo i 2 documenti principali. Come in precedenza, viene impostato Select per specificare quali campi devono essere visualizzati.

Ecco i risultati:

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

Infine, vogliamo trovare tutti i nomi degli alberghi che corrispondono alla parola "hotel":

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

WriteDocuments(results);

Di seguito sono riportati i risultati, che includono tutti i campi, poiché non è stata specificata la proprietà Select:

	HotelId: 3
	Name: Triple Landscape Hotel
	...

Questo passaggio completa l'esercitazione. **Passaggi successivi forniscono risorse aggiuntive per altre informazioni sulle Ricerca cognitiva di Azure.

Passaggi successivi