Esempio n. 1
0
        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);
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
 public IQueryable <Product> PrepareQuery(CatalogSearchQuery searchQuery, IQueryable <Product> baseQuery = null)
 {
     return(GetProductQuery(searchQuery, baseQuery));
 }
Esempio n. 4
0
        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)
 {
 }
Esempio n. 6
0
        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));
        }
Esempio n. 7
0
        public IQueryable <Product> PrepareQuery(CatalogSearchQuery searchQuery, IQueryable <Product> baseQuery = null)
        {
            var linqCatalogSearchService = _services.ResolveNamed <ICatalogSearchService>("linq");

            return(linqCatalogSearchService.PrepareQuery(searchQuery, baseQuery));
        }