/// <summary> /// Executes an advanced search using pagination, sorting, filters, facets, and highlighting. /// </summary> /// <param name="searchText">The text used for the search. When empty all results are returned.</param> /// <param name="totalRecords">The returned total number of records in the results</param> /// <param name="currentPage">The current page of results to return</param> /// <param name="pageSize">The size of the result set to return (default=20). Maximum is 1000.</param> /// <param name="filter"> /// A set of comma or semicolon separated field names to searching. /// Only fields marked with the 'IsSearchable' attribute can be included. /// The default is empty and all searchable fields will be searched. /// /// /// </param> /// <param name="selectFields"></param> /// A set of comma or semicolon separated field names to return values for. /// Default is empty and will return all field values /// <param name="orderBy"> /// A set of comma or semicolon separated sort terms. /// Default is empty and will return results sorted by score relevance. /// For example, OrganisationName, SicName DESC /// Only fields marked with the 'IsSortable' attribute can be included. /// </param> /// <param name="facets"> /// Specifies the facets to query and returns the facet results /// The default is empty and no facets will be applied. /// Only fields marked with the 'IsFacetable' attribute can be included. /// Call by specifing field names as keys in the dictionary. /// The resulting dictionary for each field returns all possible values and their count for that field. /// /// /// </param> /// <param name="filter"> /// A filter expression using OData syntax (see /// https://docs.microsoft.com/en-us/rest/api/searchservice/odata-expression-syntax-for-azure-search) /// The default is empty and no filter will be applied. /// Only fields marked with the 'IsFilterable' attribute can be included. /// String comparisons are case sensitive. /// You can also use the operators '==','!=', '>=', '>', '<=', '<', '&&', '||' which will be automatically replaced with OData counterparts 'EQ','NE', 'GE', 'GT', 'LE', 'LT', 'AND', 'OR'. /// Special functions also include search.in(myfield, 'a, b, c') /// /// </param> /// <param name="highlights"> /// A set of comma or semicolon separated field names used for hit highlights. /// Only fields marked with the 'IsSearchable' attribute can be included. /// By default, Azure Search returns up to 5 highlights per field. /// The limit is configurable per field by appending - /// <max # of highlights> /// following the field name. /// For example, highlight=title-3,description-10 returns up to 3 highlighted hits from the title field and up to /// 10 hits from the description field. <max # of highlights> must be an integer between 1 and 1000 inclusive. /// </param> public async Task <PagedResult <EmployerSearchModel> > SearchAsync(string searchText, int currentPage, SearchTypes searchType, int pageSize = 20, string searchFields = null, string selectFields = null, string orderBy = null, Dictionary <string, Dictionary <object, long> > facets = null, string filter = null, string highlights = null, SearchModes searchMode = SearchModes.Any) { if (Disabled) { throw new Exception($"{nameof(AzureEmployerSearchRepository)} is disabled"); } var indexClient = await _indexClient.Value; // Execute search based on query string var sp = new SearchParameters { SearchMode = searchMode.Equals("any") ? SearchMode.Any : SearchMode.All, Top = pageSize, Skip = (currentPage - 1) * pageSize, IncludeTotalResultCount = true, QueryType = QueryType.Simple }; //Specify the fields to search if (!string.IsNullOrWhiteSpace(searchFields)) { sp.SearchFields = searchFields.SplitI().ToList(); } //Limit result fields if (!string.IsNullOrWhiteSpace(selectFields)) { sp.Select = selectFields.SplitI().ToList(); } // Define the sort type or order by relevance score if (!string.IsNullOrWhiteSpace(orderBy) && !orderBy.EqualsI("Relevance", "Relevance desc", "Relevance asc")) { sp.OrderBy = orderBy.SplitI().ToList(); } // Add filtering sp.Filter = string.IsNullOrWhiteSpace(filter) ? null : filter; //Add facets if (facets != null && facets.Count > 0) { sp.Facets = facets.Keys.ToList(); } //Execute the search var results = await indexClient.Documents.SearchAsync <EmployerSearchModel>(searchText, sp); //Return the total records var totalRecords = results.Count.Value; /* There are too many empty searches being executed (about 1200). This needs further investigation to see if/how they can be reduced */ if (!string.IsNullOrEmpty(searchText)) { var telemetryProperties = new Dictionary <string, string> { { "TimeStamp", VirtualDateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") }, { "QueryTerms", searchText }, { "ResultCount", totalRecords.ToString() }, { "SearchType", searchType.ToString() }, { "SearchParameters", HttpUtility.UrlDecode(sp.ToString()) } }; _telemetryClient?.TrackEvent("Gpg_Search", telemetryProperties); await SearchLog.WriteAsync(telemetryProperties); } //Return the facet results if (sp.Facets != null && sp.Facets.Any()) { foreach (var facetGroupKey in results.Facets.Keys) { if (facets[facetGroupKey] == null) { facets[facetGroupKey] = new Dictionary <object, long>(); } foreach (var facetResult in results.Facets[facetGroupKey]) { facets[facetGroupKey][facetResult.Value] = facetResult.Count.Value; } } } //Return the results var searchResults = new PagedResult <EmployerSearchModel> { Results = results.Results.Select(r => r.Document).ToList(), CurrentPage = currentPage, PageSize = pageSize, ActualRecordTotal = totalRecords, VirtualRecordTotal = totalRecords }; return(searchResults); }