|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using Azure.Search.Documents.Models;
using AzureSearch.ApiService;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.AddAzureSearchClient("search");
var app = builder.Build();
app.MapDefaultEndpoints();
var logger = app.Logger;
// Configure the HTTP request pipeline.
app.MapGet("/", async (SearchIndexClient searchIndexClient, CancellationToken cancellationToken) =>
{
var indexName = "my-index";
logger.LogInformation("Deleting if exists");
await DeleteIfExistsAsync(logger, indexName, searchIndexClient, cancellationToken);
logger.LogInformation("Creating index...");
await CreateIndexAsync(logger, indexName, searchIndexClient, cancellationToken);
var searchClient = searchIndexClient.GetSearchClient(indexName);
logger.LogInformation("Uploading documents...");
await UploadDocumentsAsync(logger, searchClient, cancellationToken);
logger.LogInformation("Running queries...");
await RunQueriesAsync(logger, searchClient, cancellationToken);
});
app.Run();
static async Task DeleteIfExistsAsync(ILogger logger, string indexName, SearchIndexClient searchIndexClient, CancellationToken cancellationToken)
{
try
{
if (await searchIndexClient.GetIndexAsync(indexName, cancellationToken) != null)
{
await searchIndexClient.DeleteIndexAsync(indexName, cancellationToken);
}
}
catch (RequestFailedException e) when (e.Status == 404)
{
//if exception occurred and status is "Not Found", this is work as expect
logger.LogDebug(e, "Didn't find index.");
}
}
static async Task CreateIndexAsync(ILogger logger, string indexName, SearchIndexClient indexClient, CancellationToken cancellationToken)
{
var fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(Hotel));
var definition = new SearchIndex(indexName, searchFields);
await indexClient.CreateOrUpdateIndexAsync(definition, cancellationToken: cancellationToken);
}
static async Task WriteDocumentsAsync(ILogger logger, SearchResults<Hotel> searchResults, CancellationToken cancellationToken)
{
await foreach (var result in searchResults.GetResultsAsync().WithCancellation(cancellationToken))
{
logger.LogInformation("Document {@document}", result.Document);
}
}
static async Task RunQueriesAsync(ILogger logger, SearchClient searchClient, CancellationToken cancellationToken)
{
SearchOptions options;
SearchResults<Hotel> results;
logger.LogInformation("Search the entire index for the term 'motel' and return only the HotelName field:\n");
options = new SearchOptions();
options.Select.Add("HotelName");
results = await searchClient.SearchAsync<Hotel>("motel", options, cancellationToken);
await WriteDocumentsAsync(logger, results, cancellationToken);
logger.LogInformation("Apply a filter to the index to find hotels with a room cheaper than $100 per night, and return 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 = await searchClient.SearchAsync<Hotel>("*", options, cancellationToken);
await WriteDocumentsAsync(logger, results, cancellationToken);
logger.LogInformation(
"""
Search the entire index, order by a specific field (lastRenovationDate) in descending order, take the top two results, and show only hotelName and lastRenovationDate:\n
""");
options =
new SearchOptions()
{
Size = 2
};
options.OrderBy.Add("LastRenovationDate desc");
options.Select.Add("HotelName");
options.Select.Add("LastRenovationDate");
results = await searchClient.SearchAsync<Hotel>("*", options, cancellationToken);
await WriteDocumentsAsync(logger, results, cancellationToken);
logger.LogInformation("Search the hotel names for the term 'hotel':\n");
options = new SearchOptions();
options.SearchFields.Add("HotelName");
// Adding details to select, because "Location" is not 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 = await searchClient.SearchAsync<Hotel>("hotel", options, cancellationToken);
await WriteDocumentsAsync(logger, results, cancellationToken);
}
static async Task UploadDocumentsAsync(ILogger logger, SearchClient searchClient, CancellationToken cancellationToken)
{
var hotelsJson = "hotels.json";
using var openStream = File.OpenRead(hotelsJson);
var hotels = await JsonSerializer.DeserializeAsync<List<Hotel>>(openStream, cancellationToken: cancellationToken);
if (hotels is null)
{
logger.LogError("Failed to import the index data from {file}", hotelsJson);
return;
}
var batchActions = hotels.Select(h => IndexDocumentsAction.Upload(h)).ToArray();
var batch = IndexDocumentsBatch.Create(batchActions);
try
{
IndexDocumentsResult result = await searchClient.IndexDocumentsAsync(batch, cancellationToken: cancellationToken);
}
catch (Exception ex)
{
// 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.
logger.LogError(ex, "Failed to index some of the documents");
}
logger.LogInformation("Waiting for documents to be indexed...\n");
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
}
|