/// <summary> /// Gets the jobs matching the supplied query /// </summary> /// <param name="query"></param> /// <returns></returns> public Task <JobSearchResult> ReadJobs(JobSearchQuery query) { var examineQuery = _searcher.CreateSearchCriteria(BooleanOperation.And); examineQuery.GroupedOr(new[] { "location" }, query.Locations.ToArray()) .And().GroupedOr(new[] { "contractType" }, query.ContractTypes.ToArray()) .And().GroupedOr(new[] { "salaryDisplay" }, query.PayGrades.ToArray()) .And().GroupedOr(new[] { "workPattern" }, query.WorkPatterns.ToArray()) .And().GroupedOr(new[] { "jobType" }, query.JobTypes.ToArray()) .And().GroupedOr(new[] { "department" }, query.Departments.ToArray()) .And().GroupedOr(new[] { "organisation" }, query.Organisations.ToArray()); // If any of the GroupedOr values are empty, Examine generates a Lucene query that requires no field set to no value: +() // so we need to remove that and use the rest of the generated Lucene query var generatedQuery = examineQuery.ToString(); generatedQuery = generatedQuery.Substring(34, generatedQuery.Length - 36); var modifiedQuery = generatedQuery.Replace("+()", String.Empty); if (_keywordsQueryBuilder != null) { // Search for title keywords by building up a clause that looks for any of the keywords to be present // in the job title modifiedQuery += _keywordsQueryBuilder.AnyOfTheseTermsInThisField(query.KeywordsInTitle ?? String.Empty, new SearchField() { FieldName = "title" }, true); // Search for keywords by building up a clause that looks for all of the keywords to be present // in any one of the specified fields. modifiedQuery += _keywordsQueryBuilder.AllOfTheseTermsInAnyOfTheseFields(query.Keywords ?? String.Empty, new[] { new SearchField() { FieldName = "reference" }, new SearchField() { FieldName = "title", Boost = 2 }, new SearchField() { FieldName = "organisation" }, new SearchField() { FieldName = "location" }, new SearchField() { FieldName = "jobType" }, new SearchField() { FieldName = "contractType" }, new SearchField() { FieldName = "department" }, new SearchField() { FieldName = "fullText", Boost = 0.5 } }, true); } // For the salary ranges we need a structure that Examine's fluent API can't build, so build the raw Lucene query instead if (_salaryQueryBuilder != null) { modifiedQuery += _salaryQueryBuilder.SalaryIsWithinAnyOfTheseRanges(query.SalaryRanges); } // Append a requirement that the job must not have closed if (query.ClosingDateFrom.HasValue) { modifiedQuery += " +closingDate:[" + query.ClosingDateFrom.Value.ToString("yyyyMMdd000000000") + " TO 30000000000000000]"; } var luceneQuery = _searcher.CreateSearchCriteria(BooleanOperation.And); var sortField = String.Empty; switch (query.SortBy) { case JobSearchQuery.JobsSortOrder.JobTitleAscending: case JobSearchQuery.JobsSortOrder.JobTitleDescending: sortField = "titleDisplay"; break; case JobSearchQuery.JobsSortOrder.LocationAscending: case JobSearchQuery.JobsSortOrder.LocationDescending: sortField = "locationDisplay"; break; case JobSearchQuery.JobsSortOrder.SalaryRangeAscending: case JobSearchQuery.JobsSortOrder.SalaryRangeDescending: sortField = "salarySort"; break; case JobSearchQuery.JobsSortOrder.WorkPatternAscending: case JobSearchQuery.JobsSortOrder.WorkPatternDescending: sortField = "workPattern"; break; case JobSearchQuery.JobsSortOrder.ClosingDateAscending: case JobSearchQuery.JobsSortOrder.ClosingDateDescending: sortField = "closingDate"; break; } if (String.IsNullOrWhiteSpace(modifiedQuery)) { modifiedQuery = "__NodeId:[0 TO 999999]"; } try { switch (query.SortBy) { case JobSearchQuery.JobsSortOrder.JobTitleAscending: case JobSearchQuery.JobsSortOrder.LocationAscending: case JobSearchQuery.JobsSortOrder.SalaryRangeAscending: case JobSearchQuery.JobsSortOrder.WorkPatternAscending: case JobSearchQuery.JobsSortOrder.ClosingDateAscending: luceneQuery.RawQuery(modifiedQuery).OrderBy(sortField); break; case JobSearchQuery.JobsSortOrder.JobTitleDescending: case JobSearchQuery.JobsSortOrder.LocationDescending: case JobSearchQuery.JobsSortOrder.SalaryRangeDescending: case JobSearchQuery.JobsSortOrder.WorkPatternDescending: case JobSearchQuery.JobsSortOrder.ClosingDateDescending: luceneQuery.RawQuery(modifiedQuery).OrderByDescending(sortField); break; default: luceneQuery.RawQuery(modifiedQuery); break; } var results = _searcher.Search(luceneQuery); var searchResult = new JobSearchResult() { TotalJobs = results.Count() }; IEnumerable <SearchResult> selectedResults; if (query.PageSize.HasValue) { selectedResults = results.Skip((query.CurrentPage - 1) * query.PageSize.Value).Take(query.PageSize.Value); } else { selectedResults = results; } searchResult.Jobs = BuildJobsFromExamineResults(selectedResults); return(Task.FromResult(searchResult)); } catch (ParseException exception) { exception.Data.Add("Reference", query.JobReference); exception.Data.Add("Job types", String.Join(",", query.JobTypes.ToArray())); exception.Data.Add("Keywords in title", query.KeywordsInTitle); exception.Data.Add("Keywords", query.Keywords); exception.Data.Add("Locations", String.Join(",", query.Locations.ToArray())); exception.Data.Add("Organisations", String.Join(",", query.Organisations.ToArray())); exception.Data.Add("Salary ranges", String.Join(",", query.SalaryRanges.ToArray())); exception.Data.Add("Contract types", String.Join(",", query.ContractTypes.ToArray())); exception.Data.Add("Work patterns", String.Join(",", query.WorkPatterns.ToArray())); exception.Data.Add("Sort", query.SortBy); exception.Data.Add("Generated query", modifiedQuery); exception.ToExceptionless().Submit(); var errorForLog = new StringBuilder($"Lucene.net could not parse {modifiedQuery} generated from parameters:").Append(Environment.NewLine) .Append("Reference:").Append(query.JobReference).Append(Environment.NewLine) .Append("Job types:").Append(String.Join(",", query.JobTypes.ToArray())).Append(Environment.NewLine) .Append("Keywords in title:").Append(query.KeywordsInTitle).Append(Environment.NewLine) .Append("Keywords:").Append(query.Keywords).Append(Environment.NewLine) .Append("Locations:").Append(String.Join(",", query.Locations.ToArray())).Append(Environment.NewLine) .Append("Organisations:").Append(String.Join(",", query.Organisations.ToArray())).Append(Environment.NewLine) .Append("Salary ranges:").Append(String.Join(",", query.SalaryRanges.ToArray())).Append(Environment.NewLine) .Append("Contract types:").Append(String.Join(",", query.ContractTypes.ToArray())).Append(Environment.NewLine) .Append("Work patterns:").Append(String.Join(",", query.WorkPatterns.ToArray())).Append(Environment.NewLine) .Append("Sort:").Append(query.SortBy).Append(Environment.NewLine); _log.Error(errorForLog.ToString()); return(Task.FromResult(new JobSearchResult())); } }