Руководство. Заказ результатов поиска с помощью пакета SDK для .NET

В рамках этой серии учебников результаты возвращаются и отображаются в порядке по умолчанию. В этом руководстве вы добавите условия первичной и вторичной сортировки. В качестве альтернативы упорядочиванию на основе числовых значений в последнем примере показано, как ранжировать результаты на основе пользовательского профиля оценки. Мы также немного углубимся в отображение сложных типов.

В этом руководстве вы узнаете, как:

  • Упорядочивание результатов на основе одного свойства
  • Упорядочивание результатов на основе нескольких свойств
  • Порядок результатов на основе профиля оценки

Обзор

В этом руководстве расширяется проект бесконечной прокрутки, созданный в руководстве по добавлению разбиения на страницы в результаты поиска .

Готовую версию кода для этого руководства можно найти в следующем проекте:

Предпосылки

Упорядочивание результатов на основе одного свойства

При упорядочении результатов на основе одного свойства, например рейтинга отеля, мы не только хотим упорядоченных результатов, мы также хотим подтверждения правильности упорядочения. Добавление поля "Рейтинг" в результаты позволяет подтвердить правильность сортировки результатов.

В этом упражнении мы также добавим немного больше к отображению результатов: самый дешевый тариф номера и самый дорогой тариф номера для каждого отеля.

Для оформления заказов не нужно менять ни одну из моделей. Только представление и контроллер требуют обновлений. Начните с открытия домашнего контроллера.

Добавление свойства OrderBy в параметры поиска

  1. В HomeController.cs добавьте параметр OrderBy и добавьте свойство Rating с порядком сортировки по убыванию. В методе Index(SearchData model) добавьте следующую строку в параметры поиска.

    options.OrderBy.Add("Rating desc");
    

    Замечание

    Порядок по умолчанию возрастает, хотя вы можете добавить asc в свойство, чтобы сделать это ясно. Порядок убывания указывается путем добавления desc.

  2. Теперь запустите приложение и введите любой распространенный поисковый термин. Результаты могут быть или не быть в правильном порядке, так как ни вы, как разработчик, ни пользователь, не имеют простого способа проверить результаты!

  3. Давайте сделаем ясно, что результаты упорядочены по рейтингу. Сначала замените классы box1 и box2 в файле hotels.css следующими классами (эти классы являются новыми, которые нам нужны для этого руководства).

    textarea.box1A {
        width: 324px;
        height: 32px;
        border: none;
        background-color: azure;
        font-size: 14pt;
        color: blue;
        padding-left: 5px;
        text-align: left;
    }
    
    textarea.box1B {
        width: 324px;
        height: 32px;
        border: none;
        background-color: azure;
        font-size: 14pt;
        color: blue;
        text-align: right;
        padding-right: 5px;
    }
    
    textarea.box2A {
        width: 324px;
        height: 32px;
        border: none;
        background-color: azure;
        font-size: 12pt;
        color: blue;
        padding-left: 5px;
        text-align: left;
    }
    
    textarea.box2B {
        width: 324px;
        height: 32px;
        border: none;
        background-color: azure;
        font-size: 12pt;
        color: blue;
        text-align: right;
        padding-right: 5px;
    }
    
    textarea.box3 {
        width: 648px;
        height: 100px;
        border: none;
        background-color: azure;
        font-size: 12pt;
        padding-left: 5px;
        margin-bottom: 24px;
    }
    

    Подсказка

    Браузеры обычно кэшируют css-файлы, и это может привести к использованию старого css-файла, а изменения игнорируются. Хорошим способом является добавление строки запроса с параметром версии в ссылку. Рассмотрим пример.

      <link rel="stylesheet" href="~/css/hotels.css?v1.1" />
    

    Обновите номер версии, если вы считаете, что старый css-файл используется браузером.

  4. Добавьте свойство Rating в параметр Select в метод Index(SearchData model), чтобы результаты включали следующие три поля:

    options.Select.Add("HotelName");
    options.Select.Add("Description");
    options.Select.Add("Rating");
    
  5. Откройте представление (index.cshtml) и замените цикл отрисовки (<-- показать данные отеля. -->) следующим кодом.

    <!-- Show the hotel data. -->
    @for (var i = 0; i < result.Count; i++)
    {
        var ratingText = $"Rating: {result[i].Document.Rating}";
    
        // Display the hotel details.
        @Html.TextArea($"name{i}", result[i].Document.HotelName, new { @class = "box1A" })
        @Html.TextArea($"rating{i}", ratingText, new { @class = "box1B" })
        @Html.TextArea($"desc{i}", fullDescription, new { @class = "box3" })
    }
    
  6. Рейтинг должен быть доступен как на первой отображаемой странице, так и на последующих страницах, которые вызываются с помощью бесконечной прокрутки. Для последней из этих двух ситуаций необходимо обновить Next действие в контроллере, а также scrolled функцию в представлении. Начиная с контроллера, измените метод Next на следующий код. Этот код создает и передает текст оценки.

    public async Task<ActionResult> Next(SearchData model)
    {
        // Set the next page setting, and call the Index(model) action.
        model.paging = "next";
        await Index(model);
    
        // Create an empty list.
        var nextHotels = new List<string>();
    
        // Add a hotel details to the list.
        await foreach (var result in model.resultList.GetResultsAsync())
        {
            var ratingText = $"Rating: {result.Document.Rating}";
            var rateText = $"Rates from ${result.Document.cheapest} to ${result.Document.expensive}";
    
            string fullDescription = result.Document.Description;
    
            // Add strings to the list.
            nextHotels.Add(result.Document.HotelName);
            nextHotels.Add(ratingText);
            nextHotels.Add(fullDescription);
        }
    
        // Rather than return a view, return the list of data.
        return new JsonResult(nextHotels);
    }
    
  7. Теперь обновите прокрученную функцию в представлении, чтобы отобразить текст оценки.

    <script>
        function scrolled() {
            if (myDiv.offsetHeight + myDiv.scrollTop >= myDiv.scrollHeight) {
                $.getJSON("/Home/Next", function (data) {
                    var div = document.getElementById('myDiv');
    
                    // Append the returned data to the current list of hotels.
                    for (var i = 0; i < data.length; i += 3) {
                        div.innerHTML += '\n<textarea class="box1A">' + data[i] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box1B">' + data[i + 1] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box3">' + data[i + 2] + '</textarea>';
                    }
                });
            }
        }
    </script>
    
  8. Теперь снова запустите приложение. Выполните поиск по любому общему термину, например wifi, и убедитесь, что результаты упорядочены по убыванию рейтинга отеля.

    Упорядочение на основе рейтинга

    Вы заметите, что несколько отелей имеют одинаковый рейтинг, и поэтому их внешний вид в дисплее снова является порядком, в котором найдены данные, что является произвольным.

    Прежде чем приступить к добавлению второго уровня упорядочения, давайте добавим код для отображения диапазона цен на номера. Мы добавляем этот код как для отображения извлечения данных из сложного типа, так и для того, чтобы мы могли обсудить результаты заказа на основе цены (самый дешевый, возможно, первый).

Добавьте диапазон цен за номер в вид

  1. Добавьте свойства, содержащие самую дешевую и самую дорогую ставку за номер, в модель Hotel.cs.

    // Room rate range
    public double cheapest { get; set; }
    public double expensive { get; set; }
    
  2. Вычислите тарифы на комнаты в конце действия Index(SearchData) в контроллере домашней страницы. Добавьте вычисления после хранения временных данных.

    // Ensure TempData is stored for the next call.
    TempData["page"] = page;
    TempData["searchfor"] = model.searchText;
    
    // Calculate the room rate ranges.
    await foreach (var result in model.resultList.GetResultsAsync())
    {
        var cheapest = 0d;
        var expensive = 0d;
    
        foreach (var room in result.Document.Rooms)
        {
            var rate = room.BaseRate;
            if (rate < cheapest || cheapest == 0)
            {
                cheapest = (double)rate;
            }
            if (rate > expensive)
            {
                expensive = (double)rate;
            }
        }
        model.resultList.Results[n].Document.cheapest = cheapest;
        model.resultList.Results[n].Document.expensive = expensive;
    }
    
  3. Добавьте свойство Rooms в параметр Select в метод действия Index(SearchData model) контроллера.

    options.Select.Add("Rooms");
    
  4. Измените цикл отрисовки в представлении, чтобы отобразить диапазон частоты для первой страницы результатов.

    <!-- Show the hotel data. -->
    @for (var i = 0; i < result.Count; i++)
    {
        var rateText = $"Rates from ${result[i].Document.cheapest} to ${result[i].Document.expensive}";
        var ratingText = $"Rating: {result[i].Document.Rating}";
    
        string fullDescription = result[i].Document.Description;
    
        // Display the hotel details.
        @Html.TextArea($"name{i}", result[i].Document.HotelName, new { @class = "box1A" })
        @Html.TextArea($"rating{i}", ratingText, new { @class = "box1B" })
        @Html.TextArea($"rates{i}", rateText, new { @class = "box2A" })
        @Html.TextArea($"desc{i}", fullDescription, new { @class = "box3" })
    }
    
  5. Измените метод Next в контроллере дома, чтобы сообщить диапазон скорости для последующих страниц результатов.

    public async Task<ActionResult> Next(SearchData model)
    {
        // Set the next page setting, and call the Index(model) action.
        model.paging = "next";
        await Index(model);
    
        // Create an empty list.
        var nextHotels = new List<string>();
    
        // Add a hotel details to the list.
        await foreach (var result in model.resultList.GetResultsAsync())
        {
            var ratingText = $"Rating: {result.Document.Rating}";
            var rateText = $"Rates from ${result.Document.cheapest} to ${result.Document.expensive}";
    
            string fullDescription = result.Document.Description;
    
            // Add strings to the list.
            nextHotels.Add(result.Document.HotelName);
            nextHotels.Add(ratingText);
            nextHotels.Add(rateText);
            nextHotels.Add(fullDescription);
        }
    
        // Rather than return a view, return the list of data.
        return new JsonResult(nextHotels);
    }
    
  6. Обновите функцию прокрутки в представлении, чтобы обрабатывать текст тарифов на номера.

    <script>
        function scrolled() {
            if (myDiv.offsetHeight + myDiv.scrollTop >= myDiv.scrollHeight) {
                $.getJSON("/Home/Next", function (data) {
                    var div = document.getElementById('myDiv');
    
                    // Append the returned data to the current list of hotels.
                    for (var i = 0; i < data.length; i += 4) {
                        div.innerHTML += '\n<textarea class="box1A">' + data[i] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box1B">' + data[i + 1] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box2A">' + data[i + 2] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box3">' + data[i + 4] + '</textarea>';
                    }
                });
            }
        }
    </script>
    
  7. Запустите приложение и убедитесь, что отображаются диапазоны цен на номера.

    Отображение диапазонов цен номеров

Свойство OrderBy параметров поиска не принимает запись, например Rooms.BaseRate, для предоставления самой дешевой стоимости номера, даже если номера уже отсортированы по тарифу. В этом случае номера не отсортированы по тарифу. Чтобы отобразить отели в образце данных, упорядоченных по стоимости номеров, необходимо отсортировать результаты в вашем домашнем контроллере и отправить эти результаты в представление в заданном порядке.

Упорядочивание результатов на основе нескольких значений

Теперь вопрос заключается в том, как различать отели с одинаковым рейтингом. Один из подходов может быть вторичной сортировкой в зависимости от времени последней реконструкции отеля, чтобы недавно отремонтированные отели появлялись выше в результатах.

  1. Чтобы добавить второй уровень упорядочивания, добавьте LastRenovationDate в результаты поиска и в параметр OrderBy в методе Index(SearchData model).

    options.Select.Add("LastRenovationDate");
    
    options.OrderBy.Add("LastRenovationDate desc");
    

    Подсказка

    Любое количество свойств можно ввести в списке OrderBy . Если у отелей одинаковые рейтинги и даты ремонта, третье свойство может быть добавлено для их различия.

  2. Снова нам нужно увидеть дату обновления в представлении, просто чтобы быть уверенными, что порядок правильный. Для такой вещи, как ремонт, вероятно, достаточно года. Измените цикл отрисовки в представлении на следующий код.

    <!-- Show the hotel data. -->
    @for (var i = 0; i < result.Count; i++)
    {
        var rateText = $"Rates from ${result[i].Document.cheapest} to ${result[i].Document.expensive}";
        var lastRenovatedText = $"Last renovated: { result[i].Document.LastRenovationDate.Value.Year}";
        var ratingText = $"Rating: {result[i].Document.Rating}";
    
        string fullDescription = result[i].Document.Description;
    
        // Display the hotel details.
        @Html.TextArea($"name{i}", result[i].Document.HotelName, new { @class = "box1A" })
        @Html.TextArea($"rating{i}", ratingText, new { @class = "box1B" })
        @Html.TextArea($"rates{i}", rateText, new { @class = "box2A" })
        @Html.TextArea($"renovation{i}", lastRenovatedText, new { @class = "box2B" })
        @Html.TextArea($"desc{i}", fullDescription, new { @class = "box3" })
    }
    
  3. Измените метод Next в контроллере дома, чтобы передать компонент года последней даты ремонта.

        public async Task<ActionResult> Next(SearchData model)
        {
            // Set the next page setting, and call the Index(model) action.
            model.paging = "next";
            await Index(model);
    
            // Create an empty list.
            var nextHotels = new List<string>();
    
            // Add a hotel details to the list.
            await foreach (var result in model.resultList.GetResultsAsync())
            {
                var ratingText = $"Rating: {result.Document.Rating}";
                var rateText = $"Rates from ${result.Document.cheapest} to ${result.Document.expensive}";
                var lastRenovatedText = $"Last renovated: {result.Document.LastRenovationDate.Value.Year}";
    
                string fullDescription = result.Document.Description;
    
                // Add strings to the list.
                nextHotels.Add(result.Document.HotelName);
                nextHotels.Add(ratingText);
                nextHotels.Add(rateText);
                nextHotels.Add(lastRenovatedText);
                nextHotels.Add(fullDescription);
            }
    
            // Rather than return a view, return the list of data.
            return new JsonResult(nextHotels);
        }
    
  4. Измените функцию scrolled в представлении, чтобы отобразить текст реновации.

    <script>
        function scrolled() {
            if (myDiv.offsetHeight + myDiv.scrollTop >= myDiv.scrollHeight) {
                $.getJSON("/Home/Next", function (data) {
                    var div = document.getElementById('myDiv');
    
                    // Append the returned data to the current list of hotels.
                    for (var i = 0; i < data.length; i += 5) {
                        div.innerHTML += '\n<textarea class="box1A">' + data[i] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box1B">' + data[i + 1] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box2A">' + data[i + 2] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box2B">' + data[i + 3] + '</textarea>';
                        div.innerHTML += '\n<textarea class="box3">' + data[i + 4] + '</textarea>';
                    }
                });
            }
        }
    </script>
    
  5. Запустите приложение. Выполните поиск по общему термину, например "пул" или "вид", и убедитесь, что отели с тем же рейтингом теперь отображаются в порядке убывания даты обновления.

    Сортировка по дате ремонта

Порядок результатов на основе профиля оценки

В примерах, приведенных в руководстве, показано, как упорядочить числовые значения (рейтинг и дата обновления), предоставляя точную последовательность упорядочивания. Тем не менее, некоторые поисковые запросы и некоторые данные не поддаются такому простому сравнению между двумя элементами данных. Для запросов полнотекстового поиска Когнитивный поиск включает концепцию ранжирования. Профили оценки можно указать, чтобы повлиять на ранжирование результатов, обеспечивая более сложные и качественные сравнения.

Профили оценки определяются в схеме индекса. В данных отелей настроено несколько профилей балльной оценки. Давайте рассмотрим, как определен профиль оценки, а затем попробуйте написать код для поиска по ним.

Определение профилей оценки

Профили оценки определяются в индексе поиска во время разработки. Индекс отелей только для чтения, размещенный корпорацией Майкрософт, имеет три профиля оценки. В этом разделе рассматриваются профили оценки и показано, как использовать их в коде.

  1. Ниже приведен профиль оценки по умолчанию для набора данных отелей, используемый при не указании параметра OrderBy или ScoringProfile . Этот профиль повышает оценку отеля, если текст поиска присутствует в имени отеля, описании или списке тегов (удобства). Обратите внимание, как веса оценки благоприятствуют определённым полям. Если текст поиска отображается в другом поле, но не указан ниже, он будет иметь вес 1. Очевидно, что чем выше оценка, тем выше результат отображается в представлении.

    {
       "name": "boostByField",
       "text": {
           "weights": {
               "Tags": 3,
               "HotelName": 2,
               "Description": 1.5,
               "Description_fr": 1.5,
           }
       }
    }
    
  2. Следующий альтернативный оценочный профиль значительно повышает оценку, если указанный параметр включает в себя один или несколько тегов (которые мы называем "удобства"). Ключевой точкой этого профиля является то, что необходимо указать параметр, содержащий текст. Если параметр пуст или не указан, возникает ошибка.

    {
        "name":"boostAmenities",
        "functions":[
            {
            "fieldName":"Tags",
            "freshness":null,
            "interpolation":"linear",
            "magnitude":null,
            "distance":null,
            "tag":{
                "tagsParameter":"amenities"
            },
            "type":"tag",
            "boost":5
            }
        ],
        "functionAggregation":0
    },
    
  3. В этом третьем профиле рейтинг отеля оказывает значительное влияние на оценку. Последняя обновленная дата также увеличит оценку, но только в том случае, если эти данные попадают в течение 730 дней (2 лет) от текущей даты.

    {
        "name":"renovatedAndHighlyRated",
        "functions":[
            {
            "fieldName":"Rating",
            "freshness":null,
            "interpolation":"linear",
            "magnitude":{
                "boostingRangeStart":0,
                "boostingRangeEnd":5,
                "constantBoostBeyondRange":false
            },
            "distance":null,
            "tag":null,
            "type":"magnitude",
            "boost":20
            },
            {
            "fieldName":"LastRenovationDate",
            "freshness":{
                "boostingDuration":"P730D"
            },
            "interpolation":"quadratic",
            "magnitude":null,
            "distance":null,
            "tag":null,
            "type":"freshness",
            "boost":10
            }
        ],
        "functionAggregation":0
    }
    

    Теперь давайте посмотрим, работают ли эти профили, как мы думаем, что они должны.

Добавление кода в представление для сравнения профилей

  1. Откройте файл index.cshtml и замените <раздел текста> следующим кодом.

    <body>
    
    @using (Html.BeginForm("Index", "Home", FormMethod.Post))
    {
        <table>
            <tr>
                <td></td>
                <td>
                    <h1 class="sampleTitle">
                        <img src="~/images/azure-logo.png" width="80" />
                        Hotels Search - Order Results
                    </h1>
                </td>
            </tr>
            <tr>
                <td></td>
                <td>
                    <!-- Display the search text box, with the search icon to the right of it. -->
                    <div class="searchBoxForm">
                        @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox" }) <input class="searchBoxSubmit" type="submit" value="">
                    </div>
    
                    <div class="searchBoxForm">
                        <b>&nbsp;Order:&nbsp;</b>
                        @Html.RadioButtonFor(m => m.scoring, "Default") Default&nbsp;&nbsp;
                        @Html.RadioButtonFor(m => m.scoring, "RatingRenovation") By numerical Rating&nbsp;&nbsp;
                        @Html.RadioButtonFor(m => m.scoring, "boostAmenities") By Amenities&nbsp;&nbsp;
                        @Html.RadioButtonFor(m => m.scoring, "renovatedAndHighlyRated") By Renovated date/Rating profile&nbsp;&nbsp;
                    </div>
                </td>
            </tr>
    
            <tr>
                <td valign="top">
                    <div id="facetplace" class="facetchecks">
    
                        @if (Model != null && Model.facetText != null)
                        {
                            <h5 class="facetheader">Amenities:</h5>
                            <ul class="facetlist">
                                @for (var c = 0; c < Model.facetText.Length; c++)
                                {
                                    <li> @Html.CheckBoxFor(m => m.facetOn[c], new { @id = "check" + c.ToString() }) @Model.facetText[c] </li>
                                }
    
                            </ul>
                        }
                    </div>
                </td>
                <td>
                    @if (Model != null && Model.resultList != null)
                    {
                        // Show the total result count.
                        <p class="sampleText">
                            @Html.DisplayFor(m => m.resultList.Count) Results <br />
                        </p>
    
                        <div id="myDiv" style="width: 800px; height: 450px; overflow-y: scroll;" onscroll="scrolled()">
    
                            <!-- Show the hotel data. -->
                            @for (var i = 0; i < Model.resultList.Results.Count; i++)
                            {
                                var rateText = $"Rates from ${Model.resultList.Results[i].Document.cheapest} to ${Model.resultList.Results[i].Document.expensive}";
                                var lastRenovatedText = $"Last renovated: { Model.resultList.Results[i].Document.LastRenovationDate.Value.Year}";
                                var ratingText = $"Rating: {Model.resultList.Results[i].Document.Rating}";
    
                                string amenities = string.Join(", ", Model.resultList.Results[i].Document.Tags);
                                string fullDescription = Model.resultList.Results[i].Document.Description;
                                fullDescription += $"\nAmenities: {amenities}";
    
                                // Display the hotel details.
                                @Html.TextArea($"name{i}", Model.resultList.Results[i].Document.HotelName, new { @class = "box1A" })
                                @Html.TextArea($"rating{i}", ratingText, new { @class = "box1B" })
                                @Html.TextArea($"rates{i}", rateText, new { @class = "box2A" })
                                @Html.TextArea($"renovation{i}", lastRenovatedText, new { @class = "box2B" })
                                @Html.TextArea($"desc{i}", fullDescription, new { @class = "box3" })
                            }
                        </div>
    
                        <script>
                            function scrolled() {
                                if (myDiv.offsetHeight + myDiv.scrollTop >= myDiv.scrollHeight) {
                                    $.getJSON("/Home/Next", function (data) {
                                        var div = document.getElementById('myDiv');
    
                                        // Append the returned data to the current list of hotels.
                                        for (var i = 0; i < data.length; i += 5) {
                                            div.innerHTML += '\n<textarea class="box1A">' + data[i] + '</textarea>';
                                            div.innerHTML += '<textarea class="box1B">' + data[i + 1] + '</textarea>';
                                            div.innerHTML += '\n<textarea class="box2A">' + data[i + 2] + '</textarea>';
                                            div.innerHTML += '<textarea class="box2B">' + data[i + 3] + '</textarea>';
                                            div.innerHTML += '\n<textarea class="box3">' + data[i + 4] + '</textarea>';
                                        }
                                    });
                                }
                            }
                        </script>
                    }
                </td>
            </tr>
        </table>
    }
    </body>
    
  2. Откройте файл SearchData.cs и замените класс SearchData следующим кодом.

    public class SearchData
    {
        public SearchData()
        {
        }
    
        // Constructor to initialize the list of facets sent from the controller.
        public SearchData(List<string> facets)
        {
            facetText = new string[facets.Count];
    
            for (int i = 0; i < facets.Count; i++)
            {
                facetText[i] = facets[i];
            }
        }
    
        // Array to hold the text for each amenity.
        public string[] facetText { get; set; }
    
        // Array to hold the setting for each amenitity.
        public bool[] facetOn { get; set; }
    
        // The text to search for.
        public string searchText { get; set; }
    
        // Record if the next page is requested.
        public string paging { get; set; }
    
        // The list of results.
        public DocumentSearchResult<Hotel> resultList;
    
        public string scoring { get; set; }       
    }
    
  3. Откройте файл hotels.css и добавьте следующие классы HTML.

    .facetlist {
        list-style: none;
    }
    
    .facetchecks {
        width: 250px;
        display: normal;
        color: #666;
        margin: 10px;
        padding: 5px;
    }
    
    .facetheader {
        font-size: 10pt;
        font-weight: bold;
        color: darkgreen;
    }
    

Добавление кода в контроллер для указания профиля оценки

  1. Откройте файл домашнего контроллера. Добавьте следующую инструкцию using (чтобы помочь при создании списков).

    using System.Linq;
    
  2. В этом примере нам нужен начальный вызов Индекса , чтобы сделать немного больше, чем просто вернуть начальное представление. Теперь метод ищет до 20 удобств для отображения в представлении.

        public async Task<ActionResult> Index()
        {
            InitSearch();
    
            // Set up the facets call in the search parameters.
            SearchOptions options = new SearchOptions();
            // Search for up to 20 amenities.
            options.Facets.Add("Tags,count:20");
    
            SearchResults<Hotel> searchResult = await _searchClient.SearchAsync<Hotel>("*", options);
    
            // Convert the results to a list that can be displayed in the client.
            List<string> facets = searchResult.Facets["Tags"].Select(x => x.Value.ToString()).ToList();
    
            // Initiate a model with a list of facets for the first view.
            SearchData model = new SearchData(facets);
    
            // Save the facet text for the next view.
            SaveFacets(model, false);
    
            // Render the view including the facets.
            return View(model);
        }
    
  3. Нам нужны два частных метода для сохранения аспектов во временном хранилище, а также для их восстановления из временного хранилища и заполнения модели.

        // Save the facet text to temporary storage, optionally saving the state of the check boxes.
        private void SaveFacets(SearchData model, bool saveChecks = false)
        {
            for (int i = 0; i < model.facetText.Length; i++)
            {
                TempData["facet" + i.ToString()] = model.facetText[i];
                if (saveChecks)
                {
                    TempData["faceton" + i.ToString()] = model.facetOn[i];
                }
            }
            TempData["facetcount"] = model.facetText.Length;
        }
    
        // Recover the facet text to a model, optionally recoving the state of the check boxes.
        private void RecoverFacets(SearchData model, bool recoverChecks = false)
        {
            // Create arrays of the appropriate length.
            model.facetText = new string[(int)TempData["facetcount"]];
            if (recoverChecks)
            {
                model.facetOn = new bool[(int)TempData["facetcount"]];
            }
    
            for (int i = 0; i < (int)TempData["facetcount"]; i++)
            {
                model.facetText[i] = TempData["facet" + i.ToString()].ToString();
                if (recoverChecks)
                {
                    model.facetOn[i] = (bool)TempData["faceton" + i.ToString()];
                }
            }
        }
    
  4. При необходимости необходимо задать параметры OrderBy и ScoringProfile . Замените существующий метод Index(SearchData model) следующим образом.

    public async Task<ActionResult> Index(SearchData model)
    {
        try
        {
            InitSearch();
    
            int page;
    
            if (model.paging != null && model.paging == "next")
            {
                // Recover the facet text, and the facet check box settings.
                RecoverFacets(model, true);
    
                // Increment the page.
                page = (int)TempData["page"] + 1;
    
                // Recover the search text.
                model.searchText = TempData["searchfor"].ToString();
            }
            else
            {
                // First search with text. 
                // Recover the facet text, but ignore the check box settings, and use the current model settings.
                RecoverFacets(model, false);
    
                // First call. Check for valid text input, and valid scoring profile.
                if (model.searchText == null)
                {
                    model.searchText = "";
                }
                if (model.scoring == null)
                {
                    model.scoring = "Default";
                }
                page = 0;
            }
    
            // Setup the search parameters.
            var options = new SearchOptions
            {
                SearchMode = SearchMode.All,
    
                // Skip past results that have already been returned.
                Skip = page * GlobalVariables.ResultsPerPage,
    
                // Take only the next page worth of results.
                Size = GlobalVariables.ResultsPerPage,
    
                // Include the total number of results.
                IncludeTotalCount = true,
            };
            // Select the data properties to be returned.
            options.Select.Add("HotelName");
            options.Select.Add("Description");
            options.Select.Add("Tags");
            options.Select.Add("Rooms");
            options.Select.Add("Rating");
            options.Select.Add("LastRenovationDate");
    
            List<string> parameters = new List<string>();
            // Set the ordering based on the user's radio button selection.
            switch (model.scoring)
            {
                case "RatingRenovation":
                    // Set the ordering/scoring parameters.
                    options.OrderBy.Add("Rating desc");
                    options.OrderBy.Add("LastRenovationDate desc");
                    break;
    
                case "boostAmenities":
                    {
                        options.ScoringProfile = model.scoring;
    
                        // Create a string list of amenities that have been clicked.
                        for (int a = 0; a < model.facetOn.Length; a++)
                        {
                            if (model.facetOn[a])
                            {
                                parameters.Add(model.facetText[a]);
                            }
                        }
    
                        if (parameters.Count > 0)
                        {
                            options.ScoringParameters.Add($"amenities-{ string.Join(',', parameters)}");
                        }
                        else
                        {
                            // No amenities selected, so set profile back to default.
                            options.ScoringProfile = "";
                        }
                    }
                    break;
    
                case "renovatedAndHighlyRated":
                    options.ScoringProfile = model.scoring;
                    break;
    
                default:
                    break;
            }
    
            // For efficiency, the search call should be asynchronous, so use SearchAsync rather than Search.
            model.resultList = await _searchClient.SearchAsync<Hotel>(model.searchText, options);
    
            // Ensure TempData is stored for the next call.
            TempData["page"] = page;
            TempData["searchfor"] = model.searchText;
            TempData["scoring"] = model.scoring;
            SaveFacets(model, true);
    
            // Calculate the room rate ranges.
            await foreach (var result in model.resultList.GetResultsAsync())
            {
                var cheapest = 0d;
                var expensive = 0d;
    
                foreach (var room in result.Document.Rooms)
                {
                    var rate = room.BaseRate;
                    if (rate < cheapest || cheapest == 0)
                    {
                        cheapest = (double)rate;
                    }
                    if (rate > expensive)
                    {
                        expensive = (double)rate;
                    }
                }
    
                result.Document.cheapest = cheapest;
                result.Document.expensive = expensive;
            }
        }
        catch
        {
            return View("Error", new ErrorViewModel { RequestId = "1" });
        }
    
        return View("Index", model);
    }
    

    Прочитайте комментарии для каждого выбора переключателя .

  5. Если вы выполнили дополнительный код для предыдущего раздела о заказе на основе нескольких свойств, нам не нужно вносить изменения в действие Next .

Запуск и тестирование приложения

  1. Запустите приложение. Вы увидите полный набор удобств в обзоре.

  2. Для упорядочивания выбор "По числовой оценке" обеспечит вам числовое упорядочение, уже реализованное в этом руководстве, при этом дата обновления будет решающим фактором среди отелей с одинаковой оценкой.

    Упорядочение

  3. Теперь попробуйте профиль "По удобствам". Сделайте различные варианты удобства и убедитесь, что отели с этими удобствами повышаются в списке результатов.

    Сортировка

  4. Попробуйте сортировку по "дате обновления/рейтингу", чтобы проверить, соответствует ли результат вашим ожиданиям. Только недавно отремонтированные отели должны получить свежий импульс.

Ресурсы

Дополнительные сведения см. в следующем разделе Добавление профилей оценки в индекс Когнитивного поиска Azure.

Общие выводы

Рассмотрим следующие выносы из этого проекта:

  • Пользователи ожидают, что результаты поиска будут упорядочены, наиболее релевантными сначала.
  • Данные должны быть структурированы таким образом, чтобы упорядочение было легко. Мы не смогли сортировать сперва по принципу "самые дешевые", так как данные не структурированы таким образом, чтобы можно было осуществить сортировку без дополнительного кода.
  • Существует множество уровней для упорядочивания, чтобы различать результаты с одинаковым значением на более высоком уровне упорядочения.
  • Некоторые результаты естественно упорядочиваются в порядке возрастания (например, расстояние от точки), а некоторые — в порядке убывания (например, оценка гостя).
  • Профили оценки можно определить, если числовые сравнения недоступны или недостаточно умны для набора данных. Оценка каждого результата поможет упорядочить и отобразить результаты интеллектуально.

Дальнейшие шаги

Вы завершили этот ряд уроков по C#. Вы должны были приобрести ценные знания об Azure Cognitive Search API.

Для получения дополнительных справочных материалов и учебников рассмотрите возможность просмотра каталога обучения Microsoft Learn или других учебников в документации Azure по Когнитивному поиску.