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

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

Informazioni sulla versione 11

Azure SDK per .NET include una libreria client di Azure.Search.Documents del team di Azure SDK equivalente funzionalmente alla libreria client precedente, Microsoft.Azure.Search. La versione 11 è più coerente in termini di programmabilità di Azure. Alcuni esempi includono AzureKeyCredential l'autenticazione della chiave e System.Text.Json.Serialization per la serializzazione JSON.

Come per le versioni precedenti, è possibile usare questa libreria per:

  • Creare e gestire indici di ricerca, origini dati, indicizzatori, set di competenze e mappe sinonimiche
  • Caricare e gestire i documenti di ricerca in un indice
  • Eseguire query, senza dover gestire i dettagli di HTTP e JSON
  • Richiamare e gestire l'arricchimento tramite intelligenza artificiale (set di competenze) e output

La libreria viene distribuita come singolo pacchetto NuGet Azure.Search.Documents, che include tutte le API usate per l'accesso a livello di codice a un servizio di ricerca.

La libreria client definisce classi come SearchIndex, SearchField, e SearchDocument, nonché operazioni quali SearchIndexClient.CreateIndex e SearchClient.Search sulle classi SearchIndexClient e SearchClient. Le classi sono organizzate negli spazi dei nomi seguenti:

Azure.Search.Documents (versione 11) è destinato alla specifica del servizio di ricerca 2020-06-30.

La libreria client non fornisce operazioni di gestione dei servizi, ad esempio la creazione e il ridimensionamento dei servizi di ricerca e la gestione delle chiavi API. Se è necessario gestire le risorse di ricerca da un'applicazione .NET, usare la libreria Microsoft.Azure.Management.Search in Azure SDK per .NET.

Eseguire l'aggiornamento alla versione 11

Se si usa la versione precedente di .NET SDK e si vuole eseguire l'aggiornamento alla versione corrente disponibile a livello generale, vedere Eseguire l'aggiornamento a .NET SDK di Ricerca di intelligenza artificiale di Azure versione 11.

Requisiti dell'SDK

Azure SDK per .NET è conforme a .NET Standard 2.0.

Applicazione di esempio

Questo articolo "illustra l'esempio", basandosi sull'esempio di codice DotNetHowTo su GitHub per illustrare i concetti fondamentali in Ricerca di intelligenza artificiale di Azure, in particolare su come creare, caricare ed eseguire query su un indice di ricerca.

Per il resto di questo articolo, si supponga che un nuovo indice denominato "hotels", popolato con alcuni documenti, con diverse query corrispondenti ai risultati.

Di seguito è riportato il programma principale, che mostra il flusso complessivo:

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

    SearchIndexClient indexClient = CreateSearchIndexClient(configuration);

    string indexName = configuration["SearchIndexName"];

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

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

    SearchClient searchClient = indexClient.GetSearchClient(indexName);

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

    SearchClient indexClientForQueries = CreateSearchClientForQueries(indexName, configuration);

    Console.WriteLine("{0}", "Run queries...\n");
    RunQueries(indexClientForQueries);

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

Di seguito è riportato uno screenshot parziale dell'output, presupponendo che l'applicazione venga eseguita con un nome di servizio e chiavi API validi:

Screenshot of the Console.WriteLine output from the sample program.

Tipi di client

La libreria client usa tre tipi di client per diverse operazioni: SearchIndexClient per creare, aggiornare o eliminare indici, SearchClient per caricare o eseguire query su un indice e SearchIndexerClient per lavorare con indicizzatori e set di competenze. Questo articolo è incentrato sui primi due.

Come minimo, tutti i client richiedono il nome o l'endpoint del servizio e una chiave API. È comune fornire queste informazioni in un file di configurazione, analogamente a quanto trovato nel appsettings.json file dell'applicazione di esempio DotNetHowTo. Per leggere dal file di configurazione, aggiungere using Microsoft.Extensions.Configuration; al programma.

L'istruzione seguente crea il client di indice usato per creare, aggiornare o eliminare indici. Accetta un endpoint di servizio e una chiave API di amministrazione.

private static SearchIndexClient CreateSearchIndexClient(IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["SearchServiceEndPoint"];
    string adminApiKey = configuration["SearchServiceAdminApiKey"];

    SearchIndexClient indexClient = new SearchIndexClient(new Uri(searchServiceEndPoint), new AzureKeyCredential(adminApiKey));
    return indexClient;
}

L'istruzione successiva crea il client di ricerca usato per caricare documenti o eseguire query. SearchClient richiede un indice. Per caricare i documenti è necessaria una chiave API di amministrazione, ma è possibile usare una chiave API di query per eseguire query.

string indexName = configuration["SearchIndexName"];

private static SearchClient CreateSearchClientForQueries(string indexName, IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["SearchServiceEndPoint"];
    string queryApiKey = configuration["SearchServiceQueryApiKey"];

    SearchClient searchClient = new SearchClient(new Uri(searchServiceEndPoint), indexName, new AzureKeyCredential(queryApiKey));
    return searchClient;
}

Nota

Se si specifica una chiave non valida per l'operazione di importazione, ad esempio una chiave di query in cui è stata richiesta una chiave di amministrazione, verrà SearchClient generata una CloudException con il messaggio di errore "Accesso negato" la prima volta che si chiama un metodo di operazione. In questo caso, controllare la chiave API.

Eliminazione dell'indice

Nelle prime fasi di sviluppo potrebbe essere necessario includere un'istruzione DeleteIndex per eliminare un indice in corso di lavoro in modo da poterlo ricreare con una definizione aggiornata. Il codice di esempio per Ricerca di intelligenza artificiale di Azure spesso include un passaggio di eliminazione in modo da poter rieseguire l'esempio.

La riga seguente chiama DeleteIndexIfExists:

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

Questo metodo usa l'oggetto specificato SearchIndexClient per verificare se l'indice esiste e, in caso affermativo, lo elimina:

private static void DeleteIndexIfExists(string indexName, SearchIndexClient indexClient)
{
    try
    {
        if (indexClient.GetIndex(indexName) != null)
        {
            indexClient.DeleteIndex(indexName);
        }
    }
    catch (RequestFailedException e) when (e.Status == 404)
    {
        // Throw an exception if the index name isn't found
        Console.WriteLine("The index doesn't exist. No deletion occurred.");

Nota

Il codice di esempio in questo articolo usa i metodi sincroni per semplicità, ma è consigliabile usare i metodi asincroni nelle proprie applicazioni per mantenerli scalabili e reattivi. Ad esempio, nel metodo precedente è possibile usare DeleteIndexAsync anziché DeleteIndex.

Creare un indice

È possibile usare SearchIndexClient per creare un indice.

Il metodo seguente crea un nuovo SearchIndex oggetto con un elenco di SearchField oggetti che definiscono lo schema del nuovo indice. Ogni campo ha un nome, un tipo di dati e diversi attributi che definiscono il comportamento della ricerca.

I campi possono essere definiti da una classe modello usando FieldBuilder. La classe FieldBuilder utilizza la reflection per creare un elenco di oggetti SearchField 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.

private static void CreateIndex(string indexName, SearchIndexClient indexClient)
{
    FieldBuilder fieldBuilder = new FieldBuilder();
    var searchFields = fieldBuilder.Build(typeof(Hotel));

    var definition = new SearchIndex(indexName, searchFields);

    indexClient.CreateOrUpdateIndex(definition);
}

Oltre ai campi, è anche possibile aggiungere profili di punteggio, suggerimenti o opzioni CORS all'indice (questi parametri vengono omessi dall'esempio per brevità). È possibile trovare altre informazioni sull'oggetto SearchIndex e sulle relative parti costitutive nell'elenco delle proprietà, nonché nelle informazioni di riferimento sull'API SearchIndexREST.

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.

Chiamare CreateIndex in Main()

Main crea un nuovo indice "hotels" chiamando il metodo precedente:

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

Usare una classe modello per la rappresentazione dei dati

L'esempio DotNetHowTo usa classi di modello per le strutture di dati Hotel, Address e Room . Hotel fa Addressriferimento a , un tipo complesso a livello singolo (un campo in più parti) e Room (una raccolta di campi in più parti).

È possibile usare questi tipi per creare e caricare l'indice e per strutturare la risposta da una query:

// Use-case: <Hotel> in a field definition
FieldBuilder fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));

// Use-case: <Hotel> in a response
private static void WriteDocuments(SearchResults<Hotel> searchResults)
{
    foreach (SearchResult<Hotel> result in searchResults.GetResults())
    {
        Console.WriteLine(result.Document);
    }

    Console.WriteLine();
}

Un approccio alternativo consiste nell'aggiungere direttamente campi a un indice. Nell'esempio seguente vengono illustrati solo alcuni campi.

 SearchIndex index = new SearchIndex(indexName)
 {
     Fields =
         {
             new SimpleField("hotelId", SearchFieldDataType.String) { IsKey = true, IsFilterable = true, IsSortable = true },
             new SearchableField("hotelName") { IsFilterable = true, IsSortable = true },
             new SearchableField("hotelCategory") { IsFilterable = true, IsSortable = true },
             new SimpleField("baseRate", SearchFieldDataType.Int32) { IsFilterable = true, IsSortable = true },
             new SimpleField("lastRenovationDate", SearchFieldDataType.DateTimeOffset) { IsFilterable = true, IsSortable = true }
         }
 };

Definizioni dei campi

Il modello di dati in .NET e lo schema di indice corrispondente devono supportare l'esperienza di ricerca che si vuole fornire all'utente finale. Ogni oggetto di primo livello in .NET, ad esempio un documento di ricerca in un indice di ricerca, 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.

All'interno di ogni classe, un campo viene definito con un tipo di dati e attributi che determinano la modalità di utilizzo. Il nome di ogni proprietà pubblica in ogni classe viene mappato a un campo con lo stesso nome nella definizione dell'indice.

Esaminare il frammento di codice seguente che esegue il pull di diverse definizioni di campo dalla classe Hotel. Si noti che Address e Rooms sono tipi C# con definizioni di classe personalizzate (fare riferimento al codice di esempio se si vuole visualizzarli). Entrambi sono tipi complessi. Per altre informazioni, vedere Come modellare tipi complessi.

public partial class Hotel
{
    [SimpleField(IsKey = true, IsFilterable = true)]
    public string HotelId { get; set; }

    [SearchableField(IsSortable = true)]
    public string HotelName { get; set; }

    [SearchableField(AnalyzerName = LexicalAnalyzerName.Values.EnLucene)]
    public string Description { get; set; }

    [SearchableField(IsFilterable = true, IsSortable = true, IsFacetable = true)]
    public string Category { get; set; }

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

    [SearchableField]
    public Address Address { get; set; }

    public Room[] Rooms { get; set; }

Scelta di una classe di campi

Quando si definiscono i campi, è possibile usare la classe base SearchField oppure è possibile usare modelli helper derivati che fungono da "modelli", con proprietà preconfigurati.

Esattamente un campo nell'indice deve fungere da chiave del documento (IsKey = true). Deve essere una stringa e deve identificare in modo univoco ogni documento. È anche necessario avere IsHidden = true, il che significa che non può essere visibile nei risultati della ricerca.

Tipo di campo Descrizione e utilizzo
SearchField Classe di base, con la maggior parte delle proprietà impostate su Null, ad eccezione del Name fatto che è obbligatorio e AnalyzerName che per impostazione predefinita è Lucene standard.
SimpleField Modello helper. Può essere qualsiasi tipo di dati, è sempre non ricercabile (viene ignorato per le query di ricerca full-text) ed è recuperabile (non è nascosto). Altri attributi sono disattivati per impostazione predefinita, ma possono essere attivati. Si potrebbe usare un oggetto SimpleField per gli ID di documento o i campi usati solo in filtri, facet o profili di punteggio. In tal caso, assicurarsi di applicare tutti gli attributi necessari per lo scenario, ad esempio IsKey = true per un ID documento. Per altre informazioni, vedere SimpleFieldAttribute.cs nel codice sorgente.
SearchableField Modello helper. Deve essere una stringa ed è sempre ricercabile e recuperabile. Altri attributi sono disattivati per impostazione predefinita, ma possono essere attivati. Poiché questo tipo di campo è ricercabile, supporta i sinonimi e tutte le proprietà dell'analizzatore. Per altre informazioni, vedere SearchableFieldAttribute.cs nel codice sorgente.

Sia che si usi l'API SearchField di base o uno dei modelli di supporto, è necessario abilitare in modo esplicito gli attributi di filtro, facet e ordinamento. Ad esempio, IsFilterable, IsSortable e IsFacetable devono essere attribuiti in modo esplicito come illustrato nell'esempio precedente.

Aggiunta di attributi di campo

Si noti che ogni campo viene decorato con attributi come IsFilterable, IsSortableIsKey, e AnalyzerName. Questi attributi vengono mappati direttamente agli attributi di campo corrispondenti in un indice di Ricerca di intelligenza artificiale di Azure. La FieldBuilder classe usa queste proprietà per costruire definizioni di campo per l'indice.

Mapping dei tipi di campo

I tipi .NET delle proprietà eseguono il mapping ai tipi di campo 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.

Hai 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'oggetto FieldBuilder di non serializzarlo nell'indice come campo. Si tratta di un ottimo modo per creare proprietà calcolate lato client che è possibile usare come helper nell'applicazione. In questo caso, la SmokingAllowed proprietà riflette se una Room qualsiasi nella Rooms raccolta consente il fumo. Se tutti sono falsi, indica che l'intero hotel non consente il fumo.

Caricare un indice

Il passaggio successivo in Main popola l'indice "hotels" appena creato. Questo popolamento di indici viene eseguito nel metodo seguente: (alcuni codici sostituiti con "..." a scopo illustrativo. Vedere la soluzione di esempio completa per il codice di popolamento completo dei dati.

private static void UploadDocuments(SearchClient searchClient)
{
    IndexDocumentsBatch<Hotel> batch = IndexDocumentsBatch.Create(
        IndexDocumentsAction.Upload(
            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)",
                        ...
                    }
                }
            }),
        IndexDocumentsAction.Upload(
            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)",
                        ...
                    }
                }
            }),
        IndexDocumentsAction.Upload(
            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)",
                        ...
                    }
                }
            }
        };

    try
    {
        IndexDocumentsResult result = searchClient.IndexDocuments(batch);
    }
    catch (Exception)
    {
        // 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}");
    }

    Console.WriteLine("Waiting for documents to be indexed...\n");
    Thread.Sleep(2000);

Questo metodo è costituito da quattro parti. Il primo crea una matrice di tre Hotel oggetti ognuno con tre Room oggetti che fungeranno da dati di input da caricare nell'indice. Questi dati sono hardcoded per motivi di semplicità. In un'applicazione effettiva, è probabile che i dati provengano da un'origine dati esterna, ad esempio un database SQL.

La seconda parte crea un oggetto IndexDocumentsBatch contenente i documenti. Specificare l'operazione che si vuole applicare al batch al momento della creazione, in questo caso chiamando IndexDocumentsAction.Upload. Il batch viene quindi caricato nell'indice di Ricerca di intelligenza artificiale di Azure dal IndexDocuments 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 IndexDocumentsAction.Merge, IndexDocumentsAction.MergeOrUpload o IndexDocumentsAction.Delete. È anche possibile combinare operazioni diverse in un singolo batch chiamando IndexBatch.New, che accetta una raccolta di IndexDocumentsAction oggetti, ognuno dei quali indica a Ricerca di intelligenza artificiale di Azure di eseguire una determinata operazione su un documento. È possibile creare ogni oggetto IndexDocumentsAction con la propria operazione chiamando il metodo corrispondente, ad esempio IndexDocumentsAction.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 non riesce a indicizzare alcuni dei documenti nel batch, viene generata un'eccezione RequestFailedException . Un'eccezione può verificarsi se si esegue l'indicizzazione dei documenti mentre il servizio è sottoposto a un 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. Un'alternativa consiste nell'usare SearchIndexingBufferedSender per l'invio in batch intelligente, lo scaricamento automatico e i tentativi per le azioni di indicizzazione non riuscite. Per altre informazioni sul contesto, vedere questo esempio .

Infine, il metodo UploadDocuments ritarda per due secondi. L'indicizzazione avviene in modo asincrono nel servizio di ricerca, 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.

Chiamare UploadDocuments in Main()

Il frammento di codice seguente configura un'istanza di SearchClient usando il GetSearchClient metodo di indexClient. IndexClient usa una chiave API di amministrazione nelle richieste, necessaria per il caricamento o l'aggiornamento dei documenti.

Un approccio alternativo consiste nel chiamare SearchClient direttamente, passando una chiave API di amministrazione in AzureKeyCredential.

SearchClient searchClient = indexClient.GetSearchClient(indexName);

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

Esegui query

Innanzitutto, configurare un SearchClient oggetto che legge l'endpoint di servizio e la chiave API di query da appsettings.json:

private static SearchClient CreateSearchClientForQueries(string indexName, IConfigurationRoot configuration)
{
    string searchServiceEndPoint = configuration["SearchServiceEndPoint"];
    string queryApiKey = configuration["SearchServiceQueryApiKey"];

    SearchClient searchClient = new SearchClient(new Uri(searchServiceEndPoint), indexName, new AzureKeyCredential(queryApiKey));
    return searchClient;
}

In secondo luogo, definire un metodo che invia una richiesta di query.

Ogni volta che il metodo esegue una query, crea un nuovo SearchOptions oggetto . 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 si imposta la Filterproprietà , Selecte OrderBy per query diverse. Per altre informazioni sulla sintassi dell'espressione di query di ricerca, sintassi di query semplice.

Il passaggio successivo è l'esecuzione di query. L'esecuzione della ricerca viene eseguita usando il SearchClient.Search metodo . Per ogni query, passare il testo di ricerca da usare come stringa (o "*" se non è presente testo di ricerca), più le opzioni di ricerca create in precedenza. Viene inoltre specificato Hotel come parametro di tipo per SearchClient.Search, che indica all'SDK di deserializzare i documenti nei risultati della ricerca in oggetti di tipo Hotel.

private static void RunQueries(SearchClient searchClient)
{
    SearchOptions options;
    SearchResults<Hotel> results;

    Console.WriteLine("Query 1: Search for 'motel'. Return only the HotelName in results:\n");

    options = new SearchOptions();
    options.Select.Add("HotelName");

    results = searchClient.Search<Hotel>("motel", options);

    WriteDocuments(results);

    Console.Write("Query 2: Apply a filter to find hotels with rooms cheaper than $100 per night, ");
    Console.WriteLine("returning the HotelId and Description:\n");

    options = new SearchOptions()
    {
        Filter = "Rooms/any(r: r/BaseRate lt 100)"
    };
    options.Select.Add("HotelId");
    options.Select.Add("Description");

    results = searchClient.Search<Hotel>("*", options);

    WriteDocuments(results);

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

    options =
        new SearchOptions()
        {
            Size = 2
        };
    options.OrderBy.Add("LastRenovationDate desc");
    options.Select.Add("HotelName");
    options.Select.Add("LastRenovationDate");

    results = searchClient.Search<Hotel>("*", options);

    WriteDocuments(results);

    Console.WriteLine("Query 4: Search the HotelName field for the term 'hotel':\n");

    options = new SearchOptions();
    options.SearchFields.Add("HotelName");

    //Adding details to select, because "Location" isn't supported yet when deserializing search result to "Hotel"
    options.Select.Add("HotelId");
    options.Select.Add("HotelName");
    options.Select.Add("Description");
    options.Select.Add("Category");
    options.Select.Add("Tags");
    options.Select.Add("ParkingIncluded");
    options.Select.Add("LastRenovationDate");
    options.Select.Add("Rating");
    options.Select.Add("Address");
    options.Select.Add("Rooms");

    results = searchClient.Search<Hotel>("hotel", options);

    WriteDocuments(results);
}

In terzo luogo, definire un metodo che scrive la risposta, stampando ogni documento nella console:

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

    Console.WriteLine();
}

Chiamare RunQueries in Main()

SearchClient indexClientForQueries = CreateSearchClientForQueries(indexName, configuration);

Console.WriteLine("{0}", "Running queries...\n");
RunQueries(indexClientForQueries);

Esplorare i costrutti di query

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

options = new SearchOptions();
options.Select.Add("HotelName");

results = searchClient.Search<Hotel>("motel", options);

WriteDocuments(results);

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

Name: Secret Point Motel

Name: Twin Dome Motel

Nella seconda query usare un filtro per selezionare le camere con una tariffa notturna inferiore a $ 100. Restituisce solo l'ID e la descrizione dell'hotel nei risultati:

options = new SearchOptions()
{
    Filter = "Rooms/any(r: r/BaseRate lt 100)"
};
options.Select.Add("HotelId");
options.Select.Add("Description");

results = searchClient.Search<Hotel>("*", options);

La query precedente usa un'espressione OData $filter , Rooms/any(r: r/BaseRate lt 100), per filtrare i documenti nell'indice. In questo modo viene utilizzato l'operatore any per applicare "BaseRate lt 100" a ogni elemento dell'insieme Rooms. Per altre informazioni, vedere Sintassi del filtro OData.

Nella terza query trovare i primi due hotel che sono stati ristrutturati più di recente e visualizzare il nome dell'hotel e l'ultima data di ristrutturazione. Ecco il codice:

options =
    new SearchOptions()
    {
        Size = 2
    };
options.OrderBy.Add("LastRenovationDate desc");
options.Select.Add("HotelName");
options.Select.Add("LastRenovationDate");

results = searchClient.Search<Hotel>("*", options);

WriteDocuments(results);

Nell'ultima query trovare tutti i nomi di hotel corrispondenti alla parola "hotel":

options.Select.Add("HotelId");
options.Select.Add("HotelName");
options.Select.Add("Description");
options.Select.Add("Category");
options.Select.Add("Tags");
options.Select.Add("ParkingIncluded");
options.Select.Add("LastRenovationDate");
options.Select.Add("Rating");
options.Select.Add("Address");
options.Select.Add("Rooms");

results = searchClient.Search<Hotel>("hotel", options);

WriteDocuments(results);

Questa sezione conclude questa introduzione a .NET SDK, ma non si arresta qui. La sezione successiva suggerisce altre risorse per altre informazioni sulla programmazione con Ricerca di intelligenza artificiale di Azure.

Passaggi successivi