/// <summary> /// Gets the document by identifier. /// </summary> /// <param name="documentType">Type of the document.</param> /// <param name="id">The identifier.</param> /// <returns></returns> public override IndexModelBase GetDocumentById(Type documentType, string id) { var indexName = documentType.Name.ToLower(); var request = new GetRequest(indexName, indexName, id) { }; var result = _client.Get <dynamic>(request); IndexModelBase document = new IndexModelBase(); if (result.Source != null) { Type indexModelType = Type.GetType((string)((JObject)result.Source)["indexModelType"]); if (indexModelType != null) { document = (IndexModelBase)((JObject)result.Source).ToObject(indexModelType); // return the source document as the derived type } else { document = ((JObject)result.Source).ToObject <IndexModelBase>(); // return the source document as the base type } } return(document); }
/// <summary> /// Converts Lucene document to index model. /// </summary> /// <param name="query">The query.</param> /// <param name="hit">The scoredoc.</param> /// <returns>Index model</returns> private IndexModelBase LuceneDocToIndexModel(Query query, ScoreDoc hit) { var doc = _indexSearcher.Doc(hit.Doc); IndexModelBase document = new IndexModelBase(); try { var hitJsonField = doc.GetField("JSON"); if (hitJsonField != null) { string hitJson = hitJsonField.GetStringValue(); JObject jObject = JObject.Parse(hitJson); Type indexModelType = Type.GetType($"{ jObject["IndexModelType"].ToStringSafe() }, { jObject["IndexModelAssembly"].ToStringSafe() }"); if (indexModelType != null) { document = (IndexModelBase)jObject.ToObject(indexModelType); // return the source document as the derived type } else { document = jObject.ToObject <IndexModelBase>(); // return the source document as the base type } } Explanation explanation = _indexSearcher.Explain(query, hit.Doc); document["Explain"] = explanation.ToString(); document.Score = hit.Score; return(document); } catch { } // ignore if the result if an exception resulted (most likely cause is getting a result from a non-rock index) return(null); }
/// <summary> /// Indexes the document. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="document">The document.</param> /// <param name="indexName">Name of the index.</param> /// <param name="mappingType">Type of the mapping.</param> public override void IndexDocument <T>(T document, string indexName = null, string mappingType = null) { try { Type documentType = document.GetType(); if (indexName == null) { indexName = documentType.Name.ToLower(); } if (mappingType == null) { mappingType = documentType.Name.ToLower(); } if (!_indexes.ContainsKey(mappingType)) { CreateIndex(documentType); } var index = _indexes[mappingType]; Document doc = new Document(); foreach (var typeMappingProperty in index.MappingProperties.Values) { TextField textField = new TextField(typeMappingProperty.Name, documentType.GetProperty(typeMappingProperty.Name).GetValue(document, null).ToStringSafe().ToLower(), global::Lucene.Net.Documents.Field.Store.YES); textField.Boost = typeMappingProperty.Boost; doc.Add(textField); } IndexModelBase docIndexModelBase = document as IndexModelBase; string indexValue = LuceneID(mappingType, docIndexModelBase.Id); doc.AddStringField("type", mappingType, global::Lucene.Net.Documents.Field.Store.YES); doc.AddStringField("id", docIndexModelBase.Id.ToString(), global::Lucene.Net.Documents.Field.Store.YES); doc.AddStringField("index", indexValue, global::Lucene.Net.Documents.Field.Store.YES); // Stores all the properties as JSON to retrieve object on lookup. doc.AddStoredField("JSON", document.ToJson()); // Use the analyzer in fieldAnalyzers if that field is in that dictionary, otherwise use StandardAnalyzer. var analyzer = new PerFieldAnalyzerWrapper(defaultAnalyzer: new StandardAnalyzer(_matchVersion, new CharArraySet(_matchVersion, 0, true)), fieldAnalyzers: index.FieldAnalyzers); OpenWriter(); lock ( _lockWriter ) { if (_writer != null) { _writer.UpdateDocument(new Term("index", indexValue), doc, analyzer); // Must specify analyzer because the default analyzer that is specified in indexWriterConfig is null. } } } catch (Exception ex) { HttpContext context2 = HttpContext.Current; ExceptionLogService.LogException(ex, context2); } }
/// <summary> /// Indexes the documents. /// </summary> /// <param name="document">The document.</param> public static void IndexDocument(IndexModelBase document) { foreach (var indexType in IndexContainer.Instance.Components) { var component = indexType.Value.Value; if (component.IsActive && component.IsConnected) { component.IndexDocument(document); } } }
/// <summary> /// Deletes the document. /// </summary> /// <typeparam name="T">IndexModelBase</typeparam> /// <param name="document">The document.</param> /// <param name="indexName">Name of the index.</param> public override void DeleteDocument <T>(T document, string indexName = null) { if (indexName == null) { indexName = document.GetType().Name.ToLower(); } IndexModelBase docIndexModelBase = document as IndexModelBase; OpenWriter(); lock ( _lockWriter ) { if (_writer != null) { _writer.DeleteDocuments(new Term("index", LuceneID(document.GetType().Name.ToLower(), docIndexModelBase.Id))); } } }
/// <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; if (_client != null) { ISearchResponse <dynamic> results = null; List <SearchResultModel> searchResults = new List <SearchResultModel>(); QueryContainer queryContainer = new QueryContainer(); // add and field constraints var searchDescriptor = new SearchDescriptor <dynamic>().AllIndices(); if (entities == null || entities.Count == 0) { searchDescriptor = searchDescriptor.AllTypes(); } else { var entityTypes = new List <string>(); foreach (var entityId in entities) { // get entities search model name var entityType = new EntityTypeService(new RockContext()).Get(entityId); entityTypes.Add(entityType.IndexModelType.Name.ToLower()); // 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()) { entityTypes.Add("businessindex"); } } searchDescriptor = searchDescriptor.Type(string.Join(",", entityTypes)); // todo: consider adding indexmodeltype to the entity cache } QueryContainer matchQuery = null; if (fieldCriteria != null && fieldCriteria.FieldValues?.Count > 0) { foreach (var match in fieldCriteria.FieldValues) { if (fieldCriteria.SearchType == CriteriaSearchType.Or) { matchQuery |= new MatchQuery { Field = match.Field, Query = match.Value, Boost = match.Boost }; } else { matchQuery &= new MatchQuery { Field = match.Field, Query = match.Value }; } } } switch (searchType) { case SearchType.ExactMatch: { if (!string.IsNullOrWhiteSpace(query)) { queryContainer &= new QueryStringQuery { Query = query, AnalyzeWildcard = true }; } // special logic to support emails if (query.Contains("@")) { queryContainer |= new QueryStringQuery { Query = "email:" + query, Analyzer = "whitespace" }; // analyzer = whitespace to keep the email from being parsed into 3 variables because the @ will act as a delimitor by default } // special logic to support phone search if (query.IsDigitsOnly()) { queryContainer |= new QueryStringQuery { Query = "phone:*" + query + "*", AnalyzeWildcard = true }; } // add a search for all the words as one single search term queryContainer |= new QueryStringQuery { Query = query, AnalyzeWildcard = true, PhraseSlop = 0 }; if (matchQuery != null) { queryContainer &= matchQuery; } if (size.HasValue) { searchDescriptor.Size(size.Value); } if (from.HasValue) { searchDescriptor.From(from.Value); } searchDescriptor.Query(q => queryContainer); results = _client.Search <dynamic>(searchDescriptor); break; } case SearchType.Fuzzy: { results = _client.Search <dynamic>(d => d.AllIndices().AllTypes() .Query(q => q.Fuzzy(f => f.Value(query) .Rewrite(RewriteMultiTerm.TopTermsN)) ) ); break; } case SearchType.Wildcard: { bool enablePhraseSearch = true; if (!string.IsNullOrWhiteSpace(query)) { QueryContainer wildcardQuery = null; // 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 |= new QueryStringQuery { Query = "email:*" + query + "*", Analyzer = "whitespace" }; enablePhraseSearch = false; } else { foreach (var queryTerm in queryTerms) { if (!string.IsNullOrWhiteSpace(queryTerm)) { wildcardQuery &= new QueryStringQuery { Query = queryTerm + "*", Analyzer = "whitespace", Rewrite = RewriteMultiTerm.ScoringBoolean }; // without the rewrite all results come back with the score of 1; analyzer of whitespaces says don't fancy parse things like check-in to 'check' and 'in' } } // add special logic to help boost last names if (queryTerms.Count > 1) { QueryContainer nameQuery = null; nameQuery &= new QueryStringQuery { Query = "lastName:" + queryTerms.Last() + "*", Analyzer = "whitespace", Boost = 30 }; nameQuery &= new QueryStringQuery { Query = "firstName:" + queryTerms.First() + "*", Analyzer = "whitespace" }; wildcardQuery |= nameQuery; } // special logic to support phone search if (query.IsDigitsOnly()) { wildcardQuery |= new QueryStringQuery { Query = "phoneNumbers:*" + query, Analyzer = "whitespace" }; } } queryContainer &= wildcardQuery; } // add a search for all the words as one single search term if (enablePhraseSearch) { queryContainer |= new QueryStringQuery { Query = query, AnalyzeWildcard = true, PhraseSlop = 0 }; } if (matchQuery != null) { queryContainer &= matchQuery; } if (size.HasValue) { searchDescriptor.Size(size.Value); } if (from.HasValue) { searchDescriptor.From(from.Value); } searchDescriptor.Query(q => queryContainer); var indexBoost = GlobalAttributesCache.Value("UniversalSearchIndexBoost"); if (indexBoost.IsNotNullOrWhiteSpace()) { var boostItems = indexBoost.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries); foreach (var boostItem in boostItems) { var boostParms = boostItem.Split(new char[] { '^' }); if (boostParms.Length == 2) { int boost = 1; Int32.TryParse(boostParms[1], out boost); searchDescriptor.IndicesBoost(b => b.Add(boostParms[0], boost)); } } } results = _client.Search <dynamic>(searchDescriptor); break; } } totalResultsAvailable = results.Total; // normalize the results to rock search results if (results != null) { foreach (var hit in results.Hits) { IndexModelBase document = new IndexModelBase(); try { if (hit.Source != null) { Type indexModelType = Type.GetType($"{ ((string)((JObject)hit.Source)["indexModelType"])}, { ((string)((JObject)hit.Source)["indexModelAssembly"])}"); if (indexModelType != null) { document = (IndexModelBase)((JObject)hit.Source).ToObject(indexModelType); // return the source document as the derived type } else { document = ((JObject)hit.Source).ToObject <IndexModelBase>(); // return the source document as the base type } } if (hit.Explanation != null) { document["Explain"] = hit.Explanation.ToJson(); } document.Score = hit.Score; documents.Add(document); } catch { } // ignore if the result if an exception resulted (most likely cause is getting a result from a non-rock index) } } } return(documents); }