Руководство по Упорядочивание результатов поиска с помощью пакета SDK для .NET
В этой серии руководств результаты возвращались и отображались в порядке по умолчанию. В этом руководстве вы добавите первичный и вторичный критерии сортировки. Вместо упорядочения по числовым значениям в последнем примере мы покажем, как ранжировать результаты по пользовательскому профилю повышения. Мы также немного изучим сложные типы.
В этом руководстве описано следующее:
- Упорядочение результатов по одному свойству
- упорядочение результатов по нескольким свойствам;
- Упорядочение результатов по профилю повышения
Обзор
Это руководство расширяет функционал проекта с бесконечной прокруткой, созданного в руководстве Добавление разбивки по страницам в результаты поиска.
Готовую версию кода для этого руководства можно найти в следующем проекте:
Предварительные требования
- Решение 2b-add-infinite-scroll (GitHub). Это может быть ваш собственный проект, созданный в рамках предыдущего руководства, или копия с GitHub.
Упорядочение результатов по одному свойству
При упорядочении результатов по одному свойству, например по рейтингу отелей, нам нужно не только упорядочить результаты, но и убедиться в правильности этого порядка. Добавление поля Rating к результатам позволяет убедиться в правильной сортировке результатов.
В этом упражнении мы добавим в отображение результатов еще несколько элементов: самая низкая и самая высокая цена номеров для каждого отеля.
Для поддержки упорядочения не придется менять модели. Обновления потребуются только для представления и контроллера. Для начала откройте контроллер Home.
Добавление свойства OrderBy в параметры поиска
В HomeController.cs добавьте параметр OrderBy и укажите свойство Rating с убывающим порядком сортировки. Добавьте в методе Index(SearchData model) следующую строку к параметрам поиска.
options.OrderBy.Add("Rating desc");
Примечание
По умолчанию упорядочение выполняется по возрастанию, но для наглядности вы можете добавить к свойству ключевое слово
asc
. Порядок по убыванию определяется ключевым словомdesc
.Теперь запустите приложение и введите любое слово, часто используемое в поиске. Результаты будут возвращаться в некотором порядке, правильность которого пока не могут проверить ни сами разработчики, ни пользователи.
Давайте сделаем так, чтобы упорядочение по рейтингу стало очевидным в результатах. Во-первых, замените классы 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-файлы и это может привести к тому, что будет использоваться старый файл без внесенных изменений. Чтобы обойти эту проблему, мы рекомендуем добавить в ссылку строку запроса с параметром версии. Пример:
<link rel="stylesheet" href="~/css/hotels.css?v1.1" />
Обновите номер версии, если вы считаете, что браузер использует старый CSS-файл.
Добавьте свойство Rating к параметру Select в методе Index(SearchData model) , чтобы результаты включали следующие три поля:
options.Select.Add("HotelName"); options.Select.Add("Description"); options.Select.Add("Rating");
Откройте представление (index.cshtml) и замените цикл отрисовки (с комментарием <!-- Show the hotel data. --> ) следующим кодом.
<!-- 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" }) }
Рейтинг должен быть доступен как на первой отображаемой странице, так и на всех последующих страницах, которые вызываются через механизм бесконечный прокрутки. Для второй из этих ситуаций необходимо обновить действие 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); }
Теперь обновите функцию 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 += 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>
Снова запустите приложение. Выполните поиск по любому распространенному слову, например wifi, и убедитесь, что результаты сортируются по убыванию рейтинга отелей.
Вы можете заметить, что у нескольких отелей значения рейтинга совпадают, и их положение в результатах снова зависит от расположения данных, то есть может считаться произвольным.
Прежде чем добавить второй уровень упорядочения, давайте добавим код для отображения диапазона стоимости номеров. Этот код позволит нам, во-первых, продемонстрировать извлечение данных из сложного типа, а, во-вторых, рассмотреть упорядочение результатов по цене (например, сначала самые дешевые).
Добавление диапазона стоимости номеров в представление
Добавьте в модель Hotel.cs свойства, содержащие стоимость самого дешевого и самого дорогого номера.
// Room rate range public double cheapest { get; set; } public double expensive { get; set; }
Стоимость номеров мы будем вычислять в конце действия Index(SearchData model) в контроллере Home. Разместите эти вычисления после сохранения временных данных.
// 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; }
Добавьте свойство Rooms в параметр Select в методе действия Index(SearchData model) в контроллере.
options.Select.Add("Rooms");
Измените в представлении цикл отрисовки, чтобы отображать диапазон стоимости на первой странице результатов.
<!-- 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" }) }
Измените метод Next в контроллере Home, чтобы передавать этот диапазон стоимости для последующих страниц результатов.
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); }
Обновите функцию 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 += 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>
Запустите приложение и убедитесь, что оно отображает диапазоны стоимости номеров.
Свойство OrderBy в параметрах поиска не будет принимать такие значения, как Rooms.BaseRate, для отображения самой низкой стоимости номеров, даже если номера уже отсортированы по стоимости. В этом случае номера не сортируются по стоимости. Чтобы отображать отели из тестового набора данных в порядке увеличения стоимости номеров, вам придется сортировать результаты в контроллере Home и отправлять их в представление уже в нужном порядке.
Упорядочение результатов по нескольким значениям
Теперь мы перейдем к тому, как различать отели с одинаковым рейтингом. Одним из подходов может быть вторичное упорядочивание по показателям последнего проведения ремонта в отеле, чтобы отели со свежим ремонтом оказались выше в результатах.
Чтобы добавить второй уровень упорядочивания, добавьте LastRenovationDate в результаты поиска и в OrderBy в методе Index (SearchData model) .
options.Select.Add("LastRenovationDate"); options.OrderBy.Add("LastRenovationDate desc");
Совет
В список OrderBy можно включить любое количество свойств. Если у отелей совпадают и рейтинг, и дата последнего ремонта, можно ввести третье свойство различения между этими отелями.
Теперь нам понадобится включить в представление еще и дату ремонта, чтобы контролировать правильность порядка. Для сведений о ремонте, пожалуй, будет достаточно отобразить только год. Замените цикл отрисовки в представлении следующим кодом.
<!-- 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" }) }
Измените метод Далее в контроллере Home, чтобы он отправлял значение года из параметра даты последнего ремонта.
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); }
Обновите в представлении функцию 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>
Запустите приложение. Выполните поиск по распространенному слову, например pool (бассейн) или view (вид) и убедитесь, что отели с одинаковым рейтингом теперь отображаются в порядке даты последнего ремонта.
Упорядочение результатов по профилю повышения
Пока во всех примерах этого руководства демонстрировалось использование упорядочения по числовым значениям (рейтинг и дата ремонта) с реализацией точной последовательности упорядочения. Но некоторые поисковые запросы для некоторых данных не позволяют выполнить простое сравнение двух элементов. Для запросов полнотекстового поиска Когнитивный поиск реализует концепцию ранжирования. Можно указать профили повышения, чтобы повлиять на ранжирование результатов, обеспечивая более сложные и качественные сравнения.
Профили повышения определяются в схеме индекса. Для данных об отелях создано несколько профилей повышения. Давайте рассмотрим, как определяется профиль повышения, а затем напишем код для их применения в поиске.
Определение профилей повышения
Профили повышения определяются в индексе поиска во время разработки. Индекс hotels только для чтения, размещаемый корпорацией Майкрософт, имеет три профиля повышения. В этом разделе рассматриваются профили повышения, а также показано, как использовать такие профили повышения в коде.
Ниже приведен стандартный профиль повышения для набора данных об отелях, который используется при отсутствии параметров OrderBy или ScoringProfile. Этот профиль повышает оценку отеля, если искомый текст присутствует в названии отеля, описании или списке тегов (удобств). Обратите внимание, как весовые коэффициенты повышения отдают приоритеты определенным полям. Если искомый текст встречается в другом поле, кроме перечисленных ниже, он получает весовой коэффициент 1. Очевидно, что чем выше оценка, тем выше отображается результат в представлении.
{ "name": "boostByField", "text": { "weights": { "Tags": 3, "HotelName": 2, "Description": 1.5, "Description_fr": 1.5, } } }
Следующий альтернативный профиль повышения существенно увеличивает оценку, если переданный параметр включает один или несколько тегов из списка (условно именуемого списком "удобств"). Важной характеристикой этого профиля является то, что параметр является обязательным и должен содержать текстовое значение. Если параметр пуст или не указан, возникает ошибка.
{ "name":"boostAmenities", "functions":[ { "fieldName":"Tags", "freshness":null, "interpolation":"linear", "magnitude":null, "distance":null, "tag":{ "tagsParameter":"amenities" }, "type":"tag", "boost":5 } ], "functionAggregation":0 },
В этом третьем профиле рейтинг отеля значительно влияет на оценку. Дата последнего ремонта также повышает оценку, но только если он выполнялся не ранее, чем 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 }
Давайте посмотрим, работают ли эти профили так, как задумано.
Добавление кода сравнения профилей в представление
Откройте файл index.cshtml и замените раздел <body> следующим кодом.
<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> Order: </b> @Html.RadioButtonFor(m => m.scoring, "Default") Default @Html.RadioButtonFor(m => m.scoring, "RatingRenovation") By numerical Rating @Html.RadioButtonFor(m => m.scoring, "boostAmenities") By Amenities @Html.RadioButtonFor(m => m.scoring, "renovatedAndHighlyRated") By Renovated date/Rating profile </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>
Откройте файл 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; } }
Откройте файл 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; }
Добавление кода с определением профиля оценки в контроллер
Откройте файл с контроллером Home. Добавьте следующую инструкцию using, которая поможет в создании списков.
using System.Linq;
В нашем примере первый вызов метода Index должен делать немного больше, чем просто вернуть начальное представление. Теперь этот метод извлекает до 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); }
Нам нужны два частных метода, которые сохраняют аспекты во временном хранилище и восстанавливают их из временного хранилища для заполнения модели.
// 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()]; } } }
Нам нужно настроить параметры 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); }
Прочтите комментарии для каждого из блоков выбора switch.
Нам не нужно вносить изменения в действие Next, если вы уже выполнили дополнительный код из предыдущего раздела, посвященного упорядочению по нескольким свойствам.
Запуск и тестирование приложения
Запустите приложение. Вы увидите в представлении полный набор удобств.
Если выбрать для сортировки вариант "By numerical Rating" (По числовому рейтингу), будет использоваться ранее созданное упорядочение по числовым значениям с дополнительным упорядочением по дате ремонта для отелей с одинаковым рейтингом.
Теперь попробуйте профиль By amenities (По удобствам). Попробуйте разные наборы удобств и убедитесь, что отели с наличием этих удобств оказываются выше в списке результатов.
Попробуйте вариант By Renovated date/Rating profile (По дате ремонта и профилю рейтинга) и убедитесь, что результаты соответствуют ожиданиям. Только недавно отремонтированные отели должны получать бонус за "актуальность" (freshness).
Ресурсы
Подробные сведения см. в статье Add scoring profiles to an Azure Search index (Добавление профилей повышения в индекс Когнитивного поиска Azure).
Общие выводы
По результатам этого проекта можно сделать такие выводы:
- Пользователи будут ожидать, что результаты выводятся упорядоченными и первыми отображаются самые важные из них.
- Данные нужно структурировать так, чтобы упорядочение выполнялось очень просто. Мы не смогли легко выполнить сортировку по стоимости номеров, так как структура данных не поддерживает такое упорядочение без использования дополнительного кода.
- Можно использовать много уровней упорядочения, чтобы различать результаты с одинаковыми значениями более высоких уровней упорядочения.
- Для некоторых результатов естественным будет упорядочение по возрастанию (например, расстояние от некоторой точки), а для других — по убыванию (например, оценки гостей).
- Вы можете определить профили повышения, когда числовые параметры для набора данных неприменимы или не дают осмысленных результатов. Оценка каждого результата позволяет более точно упорядочивать и отображать результаты.
Дальнейшие действия
Вы завершили серию учебников по C#, и получили ценные знания об API-интерфейсах Когнитивного поиска Azure.
Для получения дополнительных справочных материалов и учебников рассмотрите возможность просмотра каталога обучения Microsoft Learn или других руководств в документации по Когнитивный поиск Azure.