/// <summary> /// Searches the specified query. /// </summary> /// <param name="query">The query.</param> /// <param name="searchType">Type of the search.</param> /// <param name="entities">The entities.</param> /// <param name="fieldCriteria">The field criteria.</param> /// <param name="size">The size.</param> /// <param name="from">From.</param> /// <param name="totalResultsAvailable">The total results available.</param> /// <returns></returns> public override List <IndexModelBase> Search(string query, SearchType searchType, List <int> entities, SearchFieldCriteria fieldCriteria, int?size, int?from, out long totalResultsAvailable) { List <IndexModelBase> documents = new List <IndexModelBase>(); totalResultsAvailable = 0; bool allEntities = false; BooleanQuery queryContainer = new BooleanQuery(); List <string> combinedFields = new List <string>(); List <Type> indexModelTypes = new List <Type>(); Dictionary <string, Analyzer> combinedFieldAnalyzers = new Dictionary <string, Analyzer>(); using (RockContext rockContext = new RockContext()) { var entityTypeService = new EntityTypeService(rockContext); if (entities == null || entities.Count == 0) { //add all entities allEntities = true; var selectedEntityTypes = EntityTypeCache.All().Where(e => e.IsIndexingSupported && e.IsIndexingEnabled && e.FriendlyName != "Site"); foreach (var entityTypeCache in selectedEntityTypes) { entities.Add(entityTypeCache.Id); } } foreach (var entityId in entities) { // get entities search model name var entityType = entityTypeService.GetNoTracking(entityId); indexModelTypes.Add(entityType.IndexModelType); // check if this is a person model, if so we need to add two model types one for person and the other for businesses // wish there was a cleaner way to do this if (entityType.Guid == SystemGuid.EntityType.PERSON.AsGuid()) { indexModelTypes.Add(typeof(BusinessIndex)); } } indexModelTypes = indexModelTypes.Distinct().ToList(); CombineIndexTypes(indexModelTypes, out combinedFields, out combinedFieldAnalyzers); if (entities != null && entities.Count != 0 && !allEntities) { var indexModelTypesQuery = new BooleanQuery(); indexModelTypes.ForEach(f => indexModelTypesQuery.Add(new TermQuery(new Term("type", f.Name.ToLower())), Occur.SHOULD)); queryContainer.Add(indexModelTypesQuery, Occur.MUST); } } TopDocs topDocs = null; // Use the analyzer in fieldAnalyzers if that field is in that dictionary, otherwise use StandardAnalyzer. PerFieldAnalyzerWrapper analyzer = new PerFieldAnalyzerWrapper(defaultAnalyzer: new StandardAnalyzer(_matchVersion), fieldAnalyzers: combinedFieldAnalyzers); if (fieldCriteria != null && fieldCriteria.FieldValues?.Count > 0) { Occur occur = fieldCriteria.SearchType == CriteriaSearchType.And ? Occur.MUST : Occur.SHOULD; foreach (var match in fieldCriteria.FieldValues) { BooleanClause booleanClause = new BooleanClause(new TermQuery(new Term(match.Field, match.Value)), occur); booleanClause.Query.Boost = match.Boost; queryContainer.Add(booleanClause); } } switch (searchType) { case SearchType.ExactMatch: { var wordQuery = new BooleanQuery(); if (!string.IsNullOrWhiteSpace(query)) { var words = query.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (var word in words) { var innerQuery = new BooleanQuery(); combinedFields.ForEach(f => innerQuery.Add(new PrefixQuery(new Term(f, word.ToLower())), Occur.SHOULD)); wordQuery.Add(innerQuery, Occur.SHOULD); } } if (wordQuery.Count() != 0) { queryContainer.Add(wordQuery, Occur.MUST); } // special logic to support emails if (query.Contains("@")) { queryContainer.Add(new BooleanClause(new TermQuery(new Term("Email", query)), Occur.SHOULD)); } // special logic to support phone search if (query.IsDigitsOnly()) { queryContainer.Add(new BooleanClause(new WildcardQuery(new Term("PhoneNumbers", "*" + query + "*")), Occur.SHOULD)); } // add a search for all the words as one single search term foreach (var field in combinedFields) { var phraseQuery = new PhraseQuery(); phraseQuery.Add(new Term(field, query.ToLower())); queryContainer.Add(phraseQuery, Occur.SHOULD); } break; } case SearchType.Fuzzy: { foreach (var field in combinedFields) { queryContainer.Add(new FuzzyQuery(new Term(field, query.ToLower())), Occur.SHOULD); } break; } case SearchType.Wildcard: { bool enablePhraseSearch = true; if (!string.IsNullOrWhiteSpace(query)) { BooleanQuery wildcardQuery = new BooleanQuery(); // break each search term into a separate query and add the * to the end of each var queryTerms = query.Split(' ').Select(p => p.Trim()).ToList(); // special logic to support emails if (queryTerms.Count == 1 && query.Contains("@")) { wildcardQuery.Add(new WildcardQuery(new Term("Email", "*" + query.ToLower() + "*")), Occur.SHOULD); enablePhraseSearch = false; } else { foreach (var queryTerm in queryTerms) { if (!string.IsNullOrWhiteSpace(queryTerm)) { var innerQuery = new BooleanQuery(); combinedFields.ForEach(f => innerQuery.Add(new PrefixQuery(new Term(f, queryTerm.ToLower())), Occur.SHOULD)); wildcardQuery.Add(innerQuery, Occur.MUST); } } // add special logic to help boost last names if (queryTerms.Count() > 1 && (indexModelTypes.Contains(typeof(PersonIndex)) || indexModelTypes.Contains(typeof(BusinessIndex)))) { BooleanQuery nameQuery = new BooleanQuery { { new PrefixQuery(new Term("FirstName", queryTerms.First().ToLower())), Occur.MUST }, { new PrefixQuery(new Term("LastName", queryTerms.Last().ToLower())) { Boost = 30 }, Occur.MUST } }; wildcardQuery.Add(nameQuery, Occur.SHOULD); nameQuery = new BooleanQuery { { new PrefixQuery(new Term("NickName", queryTerms.First().ToLower())), Occur.MUST }, { new PrefixQuery(new Term("LastName", queryTerms.Last().ToLower())) { Boost = 30 }, Occur.MUST } }; wildcardQuery.Add(nameQuery, Occur.SHOULD); } // special logic to support phone search if (query.IsDigitsOnly()) { wildcardQuery.Add(new PrefixQuery(new Term("PhoneNumbers", queryTerms.First().ToLower())), Occur.SHOULD); } } queryContainer.Add(wildcardQuery, Occur.MUST); } // add a search for all the words as one single search term if (enablePhraseSearch) { // add a search for all the words as one single search term foreach (var field in combinedFields) { var phraseQuery = new PhraseQuery(); phraseQuery.Add(new Term(field, query.ToLower())); queryContainer.Add(phraseQuery, Occur.SHOULD); } } break; } } int returnSize = 10; if (size.HasValue) { returnSize = size.Value; } OpenReader(); if (from.HasValue) { TopScoreDocCollector collector = TopScoreDocCollector.Create(returnSize * 10, true); // Search for 10 pages with returnSize entries in each page _indexSearcher.Search(queryContainer, collector); topDocs = collector.GetTopDocs(from.Value, returnSize); } else { topDocs = _indexSearcher.Search(queryContainer, returnSize); } totalResultsAvailable = topDocs.TotalHits; if (topDocs != null) { foreach (var hit in topDocs.ScoreDocs) { var document = LuceneDocToIndexModel(queryContainer, hit); if (document != null) { documents.Add(document); } } } return(documents); }