/// <summary> /// Creates result document collection from Lucene documents. /// </summary> /// <param name="searcher">The searcher.</param> /// <param name="topDocs">The hits.</param> private void CreateDocuments(Searcher searcher, TopDocs topDocs) { // if no documents found return if (topDocs == null) return; var entries = new List<ResultDocument>(); // get total hits var totalCount = topDocs.TotalHits; var recordsToRetrieve = Results.SearchCriteria.RecordsToRetrieve; var startIndex = Results.SearchCriteria.StartingRecord; if (recordsToRetrieve > totalCount) recordsToRetrieve = totalCount; for (var index = startIndex; index < startIndex + recordsToRetrieve; index++) { if (index >= totalCount) break; var document = searcher.Doc(topDocs.ScoreDocs[index].Doc); var doc = new ResultDocument(); var documentFields = document.GetFields(); using (var fi = documentFields.GetEnumerator()) { while (fi.MoveNext()) { if (fi.Current != null) { var field = fi.Current; doc.Add(new DocumentField(field.Name, field.StringValue)); } } } entries.Add(doc); } var searchDocuments = new ResultDocumentSet { Name = "Items", Documents = entries.OfType<IDocument>().ToArray(), TotalCount = totalCount }; Results.Documents = new[] { searchDocuments }; }
public IEnumerable<IDocument> CreateDocuments(Partition partition) { if (partition == null) throw new ArgumentNullException("partition"); var documents = new ConcurrentBag<IDocument>(); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 5 }; Parallel.ForEach(partition.Keys, parallelOptions, key => { //Trace.TraceInformation(string.Format("Processing documents starting {0} of {1} - {2}%", partition.Start, partition.Total, (partition.Start * 100 / partition.Total))); if (key != null) { var doc = new ResultDocument(); IndexItem(ref doc, key); documents.Add(doc); } }); return documents; }
protected virtual void IndexItemPrices(ref ResultDocument doc, CatalogProduct item) { var evalContext = new Domain.Pricing.Model.PriceEvaluationContext { ProductIds = new[] { item.Id } }; var prices = _pricingService.EvaluateProductPrices(evalContext); foreach (var price in prices) { //var priceList = price.Pricelist; doc.Add(new DocumentField(string.Format("price_{0}_{1}", price.Currency, price.PricelistId), price.EffectiveValue, new[] { IndexStore.No, IndexType.NotAnalyzed })); doc.Add(new DocumentField(string.Format("price_{0}_{1}_value", price.Currency, price.PricelistId), (price.EffectiveValue).ToString(CultureInfo.InvariantCulture), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); } }
protected virtual void IndexItemCustomProperties(ref ResultDocument doc, CatalogProduct item) { var properties = item.CategoryId != null ? _propertyService.GetCategoryProperties(item.CategoryId) : _propertyService.GetCatalogProperties(item.CatalogId); foreach (var propValue in item.PropertyValues.Where(x => x.Value != null)) { var property = properties.FirstOrDefault(x => string.Equals(x.Name, propValue.PropertyName, StringComparison.InvariantCultureIgnoreCase) && x.ValueType == propValue.ValueType); var contentField = string.Format("__content{0}", property != null && (property.Multilanguage && !string.IsNullOrWhiteSpace(propValue.LanguageCode)) ? "_" + propValue.LanguageCode.ToLower() : string.Empty); switch (propValue.ValueType) { case PropertyValueType.LongText: case PropertyValueType.ShortText: var stringValue = propValue.Value.ToString(); if (!string.IsNullOrWhiteSpace(stringValue)) // don't index empty values { doc.Add(new DocumentField(contentField, stringValue.ToLower(), new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); } break; } if (doc.ContainsKey(propValue.PropertyName)) continue; switch (propValue.ValueType) { case PropertyValueType.Boolean: case PropertyValueType.DateTime: case PropertyValueType.Number: doc.Add(new DocumentField(propValue.PropertyName, propValue.Value, new[] { IndexStore.Yes, IndexType.Analyzed })); break; case PropertyValueType.LongText: case PropertyValueType.ShortText: doc.Add(new DocumentField(propValue.PropertyName, propValue.Value.ToString().ToLower(), new[] { IndexStore.Yes, IndexType.Analyzed })); break; } } }
protected virtual void IndexItem(ref ResultDocument doc, string productId) { var item = _itemService.GetById(productId, ItemResponseGroup.ItemProperties | ItemResponseGroup.Categories); doc.Add(new DocumentField("__key", item.Id.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); //doc.Add(new DocumentField("__loc", "en-us", new[] { IndexStore.YES, IndexType.NOT_ANALYZED })); doc.Add(new DocumentField("__type", item.GetType().Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("__sort", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("__hidden", (!item.IsActive.Value || item.MainProductId != null).ToString().ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("code", item.Code, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("name", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("startdate", item.StartDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("enddate", item.EndDate.HasValue ? item.EndDate : DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("createddate", item.CreatedDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("lastmodifieddate", item.ModifiedDate ?? DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("catalog", item.CatalogId.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection })); doc.Add(new DocumentField("__outline", item.CatalogId.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection })); var outlines = new List<string>(); if (item.CategoryId != null) { outlines.AddRange(_catalogOutlineBuilder.GetOutlines(item.CategoryId)); } //Index item direct categories links if (item.Links != null) { foreach (var link in item.Links) { if (link.CategoryId != null) { outlines.AddRange(_catalogOutlineBuilder.GetOutlines(link.CategoryId)); //doc.Add(new DocumentField(string.Format("sort{0}{1}", link.CatalogId, link.CategoryId), category.Priority, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); } } //Add outlines to search index foreach (var outline in outlines.Distinct()) { doc.Add(new DocumentField("__outline", outline.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); } //Add all linked catalogs to search index foreach (var catalogId in outlines.Select(x => x.Split('/').FirstOrDefault()).Where(x => x != null).Distinct()) { doc.Add(new DocumentField("catalog", catalogId.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); } } // Index custom properties IndexItemCustomProperties(ref doc, item); // Index item prices IndexItemPrices(ref doc, item); //Index item reviews //IndexReviews(ref doc, item); // add to content doc.Add(new DocumentField("__content", item.Name, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); doc.Add(new DocumentField("__content", item.Code, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); }
public ISearchResults Search(string scope, ISearchCriteria criteria) { // Build query var builder = (SearchQuery)_queryBuilder.BuildQuery(criteria); SearchQueryResult resultDocs; // Add some error handling //try { var searchResponse = Client.Search(scope, builder).Result; if (!searchResponse.IsSuccess) { throw new AzureSearchException(AzureSearchHelper.FormatSearchException(searchResponse)); } resultDocs = searchResponse.Body; } /* catch (Exception ex) { throw ex; } * */ // Parse documents returned var documents = new ResultDocumentSet { TotalCount = resultDocs.Count }; var docList = new List<ResultDocument>(); foreach (var indexDoc in resultDocs.Records) { var document = new ResultDocument(); foreach (var field in indexDoc.Properties.Keys) { document.Add(new DocumentField(field, indexDoc.Properties[field])); } docList.Add(document); } documents.Documents = docList.ToArray(); // Create search results object var results = new SearchResults(criteria, new[] { documents }); return results; }
public virtual ISearchResults Search(string scope, ISearchCriteria criteria) { var command = new SearchCommand(scope, criteria.DocumentType); command.Size(criteria.RecordsToRetrieve); command.From(criteria.StartingRecord); // Add spell checking // TODO: options.SpellCheck = new SpellCheckingParameters { Collate = true }; // Build query var builder = (QueryBuilder<ESDocument>)_queryBuilder.BuildQuery(criteria); SearchResult<ESDocument> resultDocs; // Add some error handling try { resultDocs = Client.Search(command, builder); } catch (Exception ex) { throw new ElasticSearchException("Search using Elastic Search server failed, check logs for more details.", ex); } // Parse documents returned var documents = new ResultDocumentSet { TotalCount = resultDocs.hits.total }; var docList = new List<ResultDocument>(); foreach (var indexDoc in resultDocs.Documents) { var document = new ResultDocument(); foreach (var field in indexDoc.Keys) document.Add(new DocumentField(field, indexDoc[field])); docList.Add(document); } documents.Documents = docList.ToArray(); // Create search results object var results = new SearchResults(criteria, new[] { documents }); // Now add facet results var groups = new List<FacetGroup>(); if (resultDocs.facets != null) { foreach (var filter in criteria.Filters) { var groupCount = 0; var group = new FacetGroup(filter.Key); if (filter is AttributeFilter) { group.FacetType = FacetTypes.Attribute; var attributeFilter = filter as AttributeFilter; var myFilter = attributeFilter; var values = myFilter.Values; if (values != null) { var key = filter.Key.ToLower(); if (!resultDocs.facets.ContainsKey(key)) continue; var facet = resultDocs.facets[key] as TermsFacetResult; if (facet != null) { foreach (var value in values) { //facet.terms var termCount = from f in facet.terms where f.term.Equals(value.Id, StringComparison.OrdinalIgnoreCase) select f.count; var enumerable = termCount as int[] ?? termCount.ToArray(); if (!enumerable.Any()) continue; //var facet = from resultFacet var newFacet = new Facet(@group, value.Id, GetDescription(value, criteria.Locale), enumerable.SingleOrDefault()); @group.Facets.Add(newFacet); } groupCount++; } } } else if (filter is PriceRangeFilter) { group.FacetType = FacetTypes.PriceRange; var rangeFilter = filter as PriceRangeFilter; if (rangeFilter != null && rangeFilter.Currency.Equals(criteria.Currency, StringComparison.OrdinalIgnoreCase)) { var myFilter = rangeFilter; var values = myFilter.Values; if (values != null) { values = rangeFilter.Values; foreach (var value in values) { var key = String.Format("{0}-{1}", myFilter.Key, value.Id).ToLower(); if (!resultDocs.facets.ContainsKey(key)) continue; var facet = resultDocs.facets[key] as FilterFacetResult; if (facet != null && facet.count > 0) { if (facet.count == 0) continue; var myFacet = new Facet( @group, value.Id, GetDescription(value, criteria.Locale), facet.count); @group.Facets.Add(myFacet); groupCount++; } } } } } else if (filter is RangeFilter) { group.FacetType = FacetTypes.Range; var myFilter = filter as RangeFilter; if (myFilter != null) { var values = myFilter.Values; if (values != null) { foreach (var value in values) { var facet = resultDocs.facets[filter.Key] as FilterFacetResult; if (facet == null || facet.count <= 0) { continue; } var myFacet = new Facet( @group, value.Id, GetDescription(value, criteria.Locale), facet.count); @group.Facets.Add(myFacet); groupCount++; } } } } else if (filter is CategoryFilter) { group.FacetType = FacetTypes.Category; var myFilter = filter as CategoryFilter; if (myFilter != null) { var values = myFilter.Values; if (values != null) { foreach (var value in values) { var key = String.Format("{0}-{1}", myFilter.Key.ToLower(), value.Id.ToLower()).ToLower(); var facet = resultDocs.facets[key] as FilterFacetResult; if (facet == null || facet.count <= 0) { continue; } var myFacet = new Facet( @group, value.Id, GetDescription(value, criteria.Locale), facet.count); @group.Facets.Add(myFacet); groupCount++; } } } } // Add only if items exist under if (groupCount > 0) { groups.Add(group); } } } results.FacetGroups = groups.ToArray(); return results; }
/// <summary> /// Creates result document collection from Lucene documents. /// </summary> /// <param name="searcher">The searcher.</param> /// <param name="topDocs">The hits.</param> private void CreateDocuments(Searcher searcher, TopDocs topDocs) { // if no documents found return if (topDocs == null) return; var entries = new List<ResultDocument>(); // get total hits var totalCount = topDocs.TotalHits; var recordsToRetrieve = Results.SearchCriteria.RecordsToRetrieve; var startIndex = Results.SearchCriteria.StartingRecord; if (recordsToRetrieve > totalCount) recordsToRetrieve = totalCount; for (var index = startIndex; index < startIndex + recordsToRetrieve; index++) { if (index >= totalCount) break; var document = searcher.Doc(topDocs.ScoreDocs[index].Doc); var doc = new ResultDocument(); var documentFields = document.GetFields(); using (var fi = documentFields.GetEnumerator()) { while (fi.MoveNext()) { if (fi.Current != null) { var field = fi.Current; // make sure document field doens't exist, if it does, simply add another value if (doc.ContainsKey(field.Name)) { var existingField = doc[field.Name] as DocumentField; if (existingField != null) existingField.AddValue(field.StringValue); } else // add new { doc.Add(new DocumentField(field.Name, field.StringValue)); } } } } entries.Add(doc); } var searchDocuments = new ResultDocumentSet { Name = "Items", Documents = entries.ToArray(), TotalCount = totalCount }; Results.Documents = new[] { searchDocuments }; }
protected virtual void IndexItemCustomProperties(ref ResultDocument doc, CatalogProduct item) { var properties = item.Properties; foreach (var propValue in item.PropertyValues.Where(x => x.Value != null)) { var propertyName = propValue.PropertyName.ToLower(); var property = properties.FirstOrDefault(x => string.Equals(x.Name, propValue.PropertyName, StringComparison.InvariantCultureIgnoreCase) && x.ValueType == propValue.ValueType); var contentField = string.Concat("__content", property != null && property.Multilanguage && !string.IsNullOrWhiteSpace(propValue.LanguageCode) ? "_" + propValue.LanguageCode.ToLower() : string.Empty); switch (propValue.ValueType) { case PropertyValueType.LongText: case PropertyValueType.ShortText: var stringValue = propValue.Value.ToString(); if (!string.IsNullOrWhiteSpace(stringValue)) // don't index empty values { doc.Add(new DocumentField(contentField, stringValue.ToLower(), new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); } break; } switch (propValue.ValueType) { case PropertyValueType.Boolean: case PropertyValueType.DateTime: case PropertyValueType.Number: doc.Add(new DocumentField(propertyName, propValue.Value, new[] { IndexStore.Yes, IndexType.Analyzed })); break; case PropertyValueType.LongText: doc.Add(new DocumentField(propertyName, propValue.Value.ToString().ToLowerInvariant(), new[] { IndexStore.Yes, IndexType.Analyzed })); break; case PropertyValueType.ShortText: // do not tokenize small values as they will be used for lookups and filters doc.Add(new DocumentField(propertyName, propValue.Value.ToString(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); break; } } }
protected virtual void IndexItem(ref ResultDocument doc, string productId) { var item = _itemService.GetById(productId, ItemResponseGroup.ItemProperties | ItemResponseGroup.Links | ItemResponseGroup.Variations); if (item == null) return; doc.Add(new DocumentField("__key", item.Id.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); //doc.Add(new DocumentField("__loc", "en-us", new[] { IndexStore.YES, IndexType.NOT_ANALYZED })); doc.Add(new DocumentField("__type", item.GetType().Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("__sort", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("__hidden", (item.IsActive != true || item.MainProductId != null).ToString().ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("code", item.Code, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("name", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("startdate", item.StartDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("enddate", item.EndDate.HasValue ? item.EndDate : DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("createddate", item.CreatedDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("lastmodifieddate", item.ModifiedDate ?? DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); var catalogs = new List<string> { item.CatalogId }; var outlines = new List<string> { item.CatalogId }; if (item.CategoryId != null) { outlines.AddRange(_catalogOutlineBuilder.GetOutlines(item.CategoryId)); } // Index item direct categories links if (item.Links != null) { foreach (var link in item.Links.Where(link => link.CategoryId != null)) { outlines.AddRange(_catalogOutlineBuilder.GetOutlines(link.CategoryId)); } // Add all linked catalogs to search index catalogs.AddRange(outlines.Select(x => x.Split('/').FirstOrDefault()).Where(x => x != null).Distinct()); } var indexStoreNotAnalyzedStringCollection = new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }; // Add catalogs to search index foreach (var catalogId in catalogs.Distinct(StringComparer.OrdinalIgnoreCase)) { doc.Add(new DocumentField("catalog", catalogId.ToLower(), indexStoreNotAnalyzedStringCollection)); } // Add outlines to search index foreach (var outline in outlines.Distinct(StringComparer.OrdinalIgnoreCase)) { doc.Add(new DocumentField("__outline", outline.ToLower(), indexStoreNotAnalyzedStringCollection)); } // Index custom properties IndexItemCustomProperties(ref doc, item); if (item.Variations != null) { if (item.Variations.Any(c => c.ProductType == "Physical")) { doc.Add(new DocumentField("producttype", "Physical", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection })); } if (item.Variations.Any(c => c.ProductType == "Digital")) { doc.Add(new DocumentField("producttype", "Digital", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection })); } foreach (var variation in item.Variations) { IndexItemCustomProperties(ref doc, variation); } } // Index item prices IndexItemPrices(ref doc, item); //Index item reviews //IndexReviews(ref doc, item); // add to content doc.Add(new DocumentField("__content", item.Name, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); doc.Add(new DocumentField("__content", item.Code, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); }
protected virtual void IndexItem(ref ResultDocument doc, string productId) { var item = _itemService.GetById(productId, ItemResponseGroup.ItemProperties | ItemResponseGroup.Variations | ItemResponseGroup.Outlines); if (item == null) return; doc.Add(new DocumentField("__key", item.Id.ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); //doc.Add(new DocumentField("__loc", "en-us", new[] { IndexStore.YES, IndexType.NOT_ANALYZED })); doc.Add(new DocumentField("__type", item.GetType().Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("__sort", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("__hidden", (item.IsActive != true || item.MainProductId != null).ToString().ToLower(), new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("code", item.Code, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("name", item.Name, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("startdate", item.StartDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("enddate", item.EndDate.HasValue ? item.EndDate : DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("createddate", item.CreatedDate, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); doc.Add(new DocumentField("lastmodifieddate", item.ModifiedDate ?? DateTime.MaxValue, new[] { IndexStore.Yes, IndexType.NotAnalyzed })); var indexStoreNotAnalyzedStringCollection = new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection }; // Add catalogs to search index var catalogs = item.Outlines .Select(o => o.Items.First().Id) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); foreach (var catalogId in catalogs) { doc.Add(new DocumentField("catalog", catalogId.ToLower(), indexStoreNotAnalyzedStringCollection)); } // Add outlines to search index var outlineStrings = GetOutlineStrings(item.Outlines); foreach (var outline in outlineStrings) { doc.Add(new DocumentField("__outline", outline.ToLower(), indexStoreNotAnalyzedStringCollection)); } // Index custom properties IndexItemCustomProperties(ref doc, item); if (item.Variations != null) { if (item.Variations.Any(c => c.ProductType == "Physical")) { doc.Add(new DocumentField("producttype", "Physical", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection })); } if (item.Variations.Any(c => c.ProductType == "Digital")) { doc.Add(new DocumentField("producttype", "Digital", new[] { IndexStore.Yes, IndexType.NotAnalyzed, IndexDataType.StringCollection })); } foreach (var variation in item.Variations) { IndexItemCustomProperties(ref doc, variation); } } // Index item prices IndexItemPrices(ref doc, item); //Index item reviews //IndexReviews(ref doc, item); // add to content doc.Add(new DocumentField("__content", item.Name, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); doc.Add(new DocumentField("__content", item.Code, new[] { IndexStore.Yes, IndexType.Analyzed, IndexDataType.StringCollection })); }
public virtual ISearchResults Search(string scope, ISearchCriteria criteria) { var command = new SearchCommand(scope, criteria.DocumentType); command.Size(criteria.RecordsToRetrieve); command.From(criteria.StartingRecord); // Add spell checking // TODO: options.SpellCheck = new SpellCheckingParameters { Collate = true }; // Build query var builder = (QueryBuilder<ESDocument>)_queryBuilder.BuildQuery(criteria); SearchResult<ESDocument> resultDocs; // Add some error handling try { resultDocs = Client.Search(command, builder); } catch (Exception ex) { throw new ElasticSearchException("Search using Elastic Search server failed, check logs for more details.", ex); } // Parse documents returned var docList = new List<ResultDocument>(); foreach (var indexDoc in resultDocs.Documents) { var document = new ResultDocument(); foreach (var field in indexDoc.Keys) document.Add(new DocumentField(field, indexDoc[field])); docList.Add(document); } var documents = new ResultDocumentSet { TotalCount = resultDocs.hits.total, Documents = docList.OfType<IDocument>().ToArray() }; // Create search results object var results = new SearchResults(criteria, new[] { documents }) { FacetGroups = CreateFacets(criteria, resultDocs.facets) }; return results; }