protected virtual async Task <IDictionary <string, FacetGroup> > GetFacetsAsync(CatalogSearchQuery searchQuery, int totalHits) { var result = new Dictionary <string, FacetGroup>(); var storeId = searchQuery.StoreId ?? _services.StoreContext.CurrentStore.Id; var languageId = searchQuery.LanguageId ?? _services.WorkContext.WorkingLanguage.Id; foreach (var key in searchQuery.FacetDescriptors.Keys) { var descriptor = searchQuery.FacetDescriptors[key]; var facets = new List <Facet>(); var kind = FacetGroup.GetKindByKey(CatalogSearchService.Scope, key); switch (kind) { case FacetGroupKind.Category: case FacetGroupKind.Brand: case FacetGroupKind.DeliveryTime: case FacetGroupKind.Rating: case FacetGroupKind.Price: if (totalHits == 0 && !descriptor.Values.Any(x => x.IsSelected)) { continue; } break; } if (kind == FacetGroupKind.Category) { var names = await GetLocalizedNames(nameof(Category), languageId); var categoryTree = await _categoryService.GetCategoryTreeAsync(0, false, storeId); var categories = categoryTree.Flatten(false); if (descriptor.MaxChoicesCount > 0) { categories = categories.Take(descriptor.MaxChoicesCount); } foreach (var category in categories) { names.TryGetValue(category.Id, out var label); facets.Add(new Facet(new FacetValue(category.Id, IndexTypeCode.Int32) { IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(category.Id)), Label = label.HasValue() ? label : category.Name, DisplayOrder = category.DisplayOrder })); } } else if (kind == FacetGroupKind.Brand) { var names = await GetLocalizedNames(nameof(Manufacturer), languageId); var customerRoleIds = _services.WorkContext.CurrentCustomer.GetRoleIds(); var manufacturersQuery = _db.Manufacturers .AsNoTracking() .ApplyStandardFilter(false, customerRoleIds, storeId); var manufacturers = descriptor.MaxChoicesCount > 0 ? await manufacturersQuery.Take(descriptor.MaxChoicesCount).ToListAsync() : await manufacturersQuery.ToListAsync(); foreach (var manu in manufacturers) { names.TryGetValue(manu.Id, out var label); facets.Add(new Facet(new FacetValue(manu.Id, IndexTypeCode.Int32) { IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(manu.Id)), Label = label.HasValue() ? label : manu.Name, DisplayOrder = manu.DisplayOrder })); } } else if (kind == FacetGroupKind.DeliveryTime) { var names = await GetLocalizedNames(nameof(DeliveryTime), languageId); var deliveryTimesQuery = _db.DeliveryTimes .AsNoTracking() .OrderBy(x => x.DisplayOrder); var deliveryTimes = descriptor.MaxChoicesCount > 0 ? await deliveryTimesQuery.Take(descriptor.MaxChoicesCount).ToListAsync() : await deliveryTimesQuery.ToListAsync(); foreach (var deliveryTime in deliveryTimes) { names.TryGetValue(deliveryTime.Id, out var label); facets.Add(new Facet(new FacetValue(deliveryTime.Id, IndexTypeCode.Int32) { IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(deliveryTime.Id)), Label = label.HasValue() ? label : deliveryTime.Name, DisplayOrder = deliveryTime.DisplayOrder })); } } else if (kind == FacetGroupKind.Price) { var count = 0; var hasActivePredefinedFacet = false; var minPrice = await _db.Products.Where(x => x.Published && !x.IsSystemProduct).MinAsync(x => (double)x.Price); var maxPrice = await _db.Products.Where(x => x.Published && !x.IsSystemProduct).MaxAsync(x => (double)x.Price); minPrice = FacetUtility.MakePriceEven(minPrice); maxPrice = FacetUtility.MakePriceEven(maxPrice); for (var i = 0; i < _priceThresholds.Length; ++i) { if (descriptor.MaxChoicesCount > 0 && facets.Count >= descriptor.MaxChoicesCount) { break; } var price = _priceThresholds[i]; if (price < minPrice) { continue; } if (price >= maxPrice) { i = int.MaxValue - 1; } var selected = descriptor.Values.Any(x => x.IsSelected && x.Value == null && x.UpperValue != null && (double)x.UpperValue == price); if (selected) { hasActivePredefinedFacet = true; } facets.Add(new Facet(new FacetValue(null, price, IndexTypeCode.Double, false, true) { DisplayOrder = ++count, IsSelected = selected })); } // Add facet for custom price range. var priceDescriptorValue = descriptor.Values.FirstOrDefault(); var customPriceFacetValue = new FacetValue( priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.Value : null, priceDescriptorValue != null && !hasActivePredefinedFacet ? priceDescriptorValue.UpperValue : null, IndexTypeCode.Double, true, true); customPriceFacetValue.IsSelected = customPriceFacetValue.Value != null || customPriceFacetValue.UpperValue != null; if (!(totalHits == 0 && !customPriceFacetValue.IsSelected)) { facets.Insert(0, new Facet("custom", customPriceFacetValue)); } } else if (kind == FacetGroupKind.Rating) { foreach (var rating in FacetUtility.GetRatings()) { var newFacet = new Facet(rating); newFacet.Value.IsSelected = descriptor.Values.Any(x => x.IsSelected && x.Value.Equals(rating.Value)); facets.Add(newFacet); } } else if (kind == FacetGroupKind.Availability || kind == FacetGroupKind.NewArrivals) { var value = descriptor.Values.FirstOrDefault(); if (value != null) { if (kind == FacetGroupKind.NewArrivals && totalHits == 0 && !value.IsSelected) { continue; } var newValue = value.Clone(); newValue.Value = true; newValue.TypeCode = IndexTypeCode.Boolean; newValue.IsRange = false; newValue.IsSelected = value.IsSelected; facets.Add(new Facet(newValue)); } } if (facets.Any(x => x.Published)) { //facets.Each(x => $"{key} {x.Value.ToString()}".Dump()); result.Add(key, new FacetGroup( CatalogSearchService.Scope, key, descriptor.Label, descriptor.IsMultiSelect, false, descriptor.DisplayOrder, facets.OrderBy(descriptor))); } } return(result); }
public async Task <CatalogSearchResult> SearchAsync(CatalogSearchQuery searchQuery, bool direct = false) { await _services.EventPublisher.PublishAsync(new CatalogSearchingEvent(searchQuery, true)); var totalHits = 0; int[] hitsEntityIds = null; IDictionary <string, FacetGroup> facets = null; if (searchQuery.Take > 0) { var query = GetProductQuery(searchQuery, null); totalHits = await query.CountAsync(); //totalHits = await query.Select(x => x.Id).Distinct().CountAsync(); // Fix paging boundaries. if (searchQuery.Skip > 0 && searchQuery.Skip >= totalHits) { searchQuery.Slice((totalHits / searchQuery.Take) * searchQuery.Take, searchQuery.Take); } if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) { var skip = searchQuery.PageIndex * searchQuery.Take; //var query2 = query // .Select(x => x.Id) // .Distinct() // .Skip(skip) // .Take(searchQuery.Take); //query2.ToQueryString().Dump(); //hitsEntityIds = query2.ToArray(); query = query .Skip(skip) .Take(searchQuery.Take); hitsEntityIds = query.Select(x => x.Id).ToArray(); } if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets) && searchQuery.FacetDescriptors.Any()) { facets = await GetFacetsAsync(searchQuery, totalHits); } } var result = new CatalogSearchResult( null, searchQuery, _db.Products, totalHits, hitsEntityIds, null, facets); var searchedEvent = new CatalogSearchedEvent(searchQuery, result); await _services.EventPublisher.PublishAsync(searchedEvent); return(searchedEvent.Result); }
public IQueryable <Product> PrepareQuery(CatalogSearchQuery searchQuery, IQueryable <Product> baseQuery = null) { return(GetProductQuery(searchQuery, baseQuery)); }
protected virtual IQueryable <Product> GetProductQuery(CatalogSearchQuery searchQuery, IQueryable <Product> baseQuery) { var ctx = new QueryBuilderContext { SearchQuery = searchQuery }; FlattenFilters(searchQuery.Filters, ctx.Filters); var query = baseQuery ?? _db.Products; query = query.Where(x => !x.IsSystemProduct); query = ApplySearchTerm(ctx, query); var productIds = GetIdList(ctx.Filters, "id"); if (productIds.Any()) { query = query.Where(x => productIds.Contains(x.Id)); } var categoryIds = GetIdList(ctx.Filters, "categoryid"); if (categoryIds.Any()) { ctx.IsGroupingRequired = true; ctx.CategoryId ??= categoryIds.First(); if (categoryIds.Count == 1 && ctx.CategoryId == 0) { // Has no category. query = query.Where(x => x.ProductCategories.Count == 0); } else { query = ApplyCategoriesFilter(query, categoryIds, null); } } var featuredCategoryIds = GetIdList(ctx.Filters, "featuredcategoryid"); var notFeaturedCategoryIds = GetIdList(ctx.Filters, "notfeaturedcategoryid"); if (featuredCategoryIds.Any()) { ctx.IsGroupingRequired = true; ctx.CategoryId ??= featuredCategoryIds.First(); query = ApplyCategoriesFilter(query, featuredCategoryIds, true); } if (notFeaturedCategoryIds.Any()) { ctx.IsGroupingRequired = true; ctx.CategoryId ??= notFeaturedCategoryIds.First(); query = ApplyCategoriesFilter(query, notFeaturedCategoryIds, false); } var manufacturerIds = GetIdList(ctx.Filters, "manufacturerid"); if (manufacturerIds.Any()) { ctx.IsGroupingRequired = true; ctx.ManufacturerId ??= manufacturerIds.First(); if (manufacturerIds.Count == 1 && ctx.ManufacturerId == 0) { // Has no manufacturer. query = query.Where(x => x.ProductManufacturers.Count == 0); } else { query = ApplyManufacturersFilter(query, manufacturerIds, null); } } var featuredManuIds = GetIdList(ctx.Filters, "featuredmanufacturerid"); var notFeaturedManuIds = GetIdList(ctx.Filters, "notfeaturedmanufacturerid"); if (featuredManuIds.Any()) { ctx.IsGroupingRequired = true; ctx.ManufacturerId ??= featuredManuIds.First(); query = ApplyManufacturersFilter(query, featuredManuIds, true); } if (notFeaturedManuIds.Any()) { ctx.IsGroupingRequired = true; ctx.ManufacturerId ??= notFeaturedManuIds.First(); query = ApplyManufacturersFilter(query, notFeaturedManuIds, false); } var tagIds = GetIdList(ctx.Filters, "tagid"); if (tagIds.Any()) { ctx.IsGroupingRequired = true; query = from p in query from pt in p.ProductTags.Where(pt => tagIds.Contains(pt.Id)) select p; } var deliverTimeIds = GetIdList(ctx.Filters, "deliveryid"); if (deliverTimeIds.Any()) { query = query.Where(x => x.DeliveryTimeId != null && deliverTimeIds.Contains(x.DeliveryTimeId.Value)); } var parentProductIds = GetIdList(ctx.Filters, "parentid"); if (parentProductIds.Any()) { query = query.Where(x => parentProductIds.Contains(x.ParentGroupedProductId)); } var conditions = GetIdList(ctx.Filters, "condition"); if (conditions.Any()) { query = query.Where(x => conditions.Contains((int)x.Condition)); } foreach (IAttributeSearchFilter filter in ctx.Filters) { if (filter is IRangeSearchFilter rf) { query = ApplyRangeFilter(ctx, query, rf); } else { // Filters that can have both range and comparison values. if (filter.FieldName == "stockquantity") { if (filter.Occurence == SearchFilterOccurence.MustNot) { query = query.Where(x => x.StockQuantity != (int)filter.Term); } else { query = query.Where(x => x.StockQuantity == (int)filter.Term); } } else if (filter.FieldName == "rating") { if (filter.Occurence == SearchFilterOccurence.MustNot) { query = query.Where(x => x.ApprovedTotalReviews != 0 && ((double)x.ApprovedRatingSum / (double)x.ApprovedTotalReviews) != (double)filter.Term); } else { query = query.Where(x => x.ApprovedTotalReviews != 0 && ((double)x.ApprovedRatingSum / (double)x.ApprovedTotalReviews) == (double)filter.Term); } } else if (filter.FieldName == "createdon") { if (filter.Occurence == SearchFilterOccurence.MustNot) { query = query.Where(x => x.CreatedOnUtc != (DateTime)filter.Term); } else { query = query.Where(x => x.CreatedOnUtc == (DateTime)filter.Term); } } else if (filter.FieldName.StartsWith("price")) { var price = Convert.ToDecimal(filter.Term); if (filter.Occurence == SearchFilterOccurence.MustNot) { query = query.Where(x => ((x.SpecialPrice.HasValue && ((!x.SpecialPriceStartDateTimeUtc.HasValue || x.SpecialPriceStartDateTimeUtc.Value < ctx.Now) && (!x.SpecialPriceEndDateTimeUtc.HasValue || x.SpecialPriceEndDateTimeUtc.Value > ctx.Now))) && (x.SpecialPrice != price)) || ((!x.SpecialPrice.HasValue || ((x.SpecialPriceStartDateTimeUtc.HasValue && x.SpecialPriceStartDateTimeUtc.Value > ctx.Now) || (x.SpecialPriceEndDateTimeUtc.HasValue && x.SpecialPriceEndDateTimeUtc.Value < ctx.Now))) && (x.Price != price)) ); } else { query = query.Where(x => ((x.SpecialPrice.HasValue && ((!x.SpecialPriceStartDateTimeUtc.HasValue || x.SpecialPriceStartDateTimeUtc.Value < ctx.Now) && (!x.SpecialPriceEndDateTimeUtc.HasValue || x.SpecialPriceEndDateTimeUtc.Value > ctx.Now))) && (x.SpecialPrice == price)) || ((!x.SpecialPrice.HasValue || ((x.SpecialPriceStartDateTimeUtc.HasValue && x.SpecialPriceStartDateTimeUtc.Value > ctx.Now) || (x.SpecialPriceEndDateTimeUtc.HasValue && x.SpecialPriceEndDateTimeUtc.Value < ctx.Now))) && (x.Price == price)) ); } } } if (filter.FieldName == "published") { query = query.Where(x => x.Published == (bool)filter.Term); } else if (filter.FieldName == "visibility") { var visibility = (ProductVisibility)filter.Term; query = visibility switch { ProductVisibility.SearchResults => query.Where(x => x.Visibility <= visibility), _ => query.Where(x => x.Visibility == visibility), }; } else if (filter.FieldName == "showonhomepage") { query = query.Where(p => p.ShowOnHomePage == (bool)filter.Term); } else if (filter.FieldName == "download") { query = query.Where(p => p.IsDownload == (bool)filter.Term); } else if (filter.FieldName == "recurring") { query = query.Where(p => p.IsRecurring == (bool)filter.Term); } else if (filter.FieldName == "shipenabled") { query = query.Where(p => p.IsShippingEnabled == (bool)filter.Term); } else if (filter.FieldName == "shipfree") { query = query.Where(p => p.IsFreeShipping == (bool)filter.Term); } else if (filter.FieldName == "taxexempt") { query = query.Where(p => p.IsTaxExempt == (bool)filter.Term); } else if (filter.FieldName == "esd") { query = query.Where(p => p.IsEsd == (bool)filter.Term); } else if (filter.FieldName == "discount") { query = query.Where(p => p.HasDiscountsApplied == (bool)filter.Term); } else if (filter.FieldName == "typeid") { query = query.Where(x => x.ProductTypeId == (int)filter.Term); } else if (filter.FieldName == "available") { query = query.Where(x => x.ManageInventoryMethodId == (int)ManageInventoryMethod.DontManageStock || (x.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && (x.StockQuantity > 0 || x.BackorderModeId != (int)BackorderMode.NoBackorders)) || (x.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStockByAttributes && x.ProductVariantAttributeCombinations.Any(pvac => pvac.StockQuantity > 0 || pvac.AllowOutOfStockOrders)) ); } } query = ApplyAclFilter(ctx, query); query = ApplyStoreFilter(ctx, query); // Not supported by EF Core 5.0. //if (ctx.IsGroupingRequired) //{ // query = // from p in query // group p by p.Id into grp // orderby grp.Key // select grp.FirstOrDefault(); //} if (ctx.IsGroupingRequired) { // Distinct is very slow if there are many products. query = query.Distinct(); } query = ApplyOrdering(ctx, query); return(query); }
/// <summary> /// Constructor to get an instance without any search hits. /// </summary> /// <param name="query">Catalog search query</param> public CatalogSearchResult(CatalogSearchQuery query) : this(null, query, null, 0, null, null, null) { }
public async Task <CatalogSearchResult> SearchAsync(CatalogSearchQuery searchQuery, bool direct = false) { Guard.NotNull(searchQuery, nameof(searchQuery)); Guard.NotNegative(searchQuery.Take, nameof(searchQuery.Take)); var provider = _indexManager.GetIndexProvider(Scope); if (!direct && provider != null) { var indexStore = provider.GetIndexStore(Scope); if (indexStore.Exists) { var searchEngine = provider.GetSearchEngine(indexStore, searchQuery); var stepPrefix = searchEngine.GetType().Name + " - "; int totalCount = 0; int[] hitsEntityIds = null; string[] spellCheckerSuggestions = null; IEnumerable <ISearchHit> searchHits; IDictionary <string, FacetGroup> facets = null; await _services.EventPublisher.PublishAsync(new CatalogSearchingEvent(searchQuery, false)); if (searchQuery.Take > 0) { using (_services.Chronometer.Step(stepPrefix + "Search")) { totalCount = await searchEngine.CountAsync(); // Fix paging boundaries. if (searchQuery.Skip > 0 && searchQuery.Skip >= totalCount) { searchQuery.Slice((totalCount / searchQuery.Take) * searchQuery.Take, searchQuery.Take); } } if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithHits)) { using (_services.Chronometer.Step(stepPrefix + "Hits")) { searchHits = await searchEngine.SearchAsync(); } hitsEntityIds = searchHits.Select(x => x.EntityId).ToArray(); } if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithFacets)) { try { using (_services.Chronometer.Step(stepPrefix + "Facets")) { facets = await searchEngine.GetFacetMapAsync(); ApplyFacetLabels(facets); } } catch (Exception ex) { Logger.Error(ex); } } } if (searchQuery.ResultFlags.HasFlag(SearchResultFlags.WithSuggestions)) { try { using (_services.Chronometer.Step(stepPrefix + "Spellcheck")) { spellCheckerSuggestions = await searchEngine.CheckSpellingAsync(); } } catch (Exception ex) { // Spell checking should not break the search. Logger.Error(ex); } } var result = new CatalogSearchResult( searchEngine, searchQuery, _db.Products, totalCount, hitsEntityIds, spellCheckerSuggestions, facets); var searchedEvent = new CatalogSearchedEvent(searchQuery, result); await _services.EventPublisher.PublishAsync(searchedEvent); return(searchedEvent.Result); } else if (searchQuery.Origin.EqualsNoCase("Search/Search")) { IndexingRequiredNotification(_services, _urlHelper); } } return(await SearchDirectAsync(searchQuery)); }
public IQueryable <Product> PrepareQuery(CatalogSearchQuery searchQuery, IQueryable <Product> baseQuery = null) { var linqCatalogSearchService = _services.ResolveNamed <ICatalogSearchService>("linq"); return(linqCatalogSearchService.PrepareQuery(searchQuery, baseQuery)); }