Example #1
0
            private static ProductSearchServiceResponse SearchProductsLocal(ProductSearchServiceRequest request)
            {
                var queryCriteria = request.QueryCriteria;

                if (queryCriteria.Context == null || queryCriteria.Context.ChannelId.HasValue == false)
                {
                    throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, "The channel identifier on the query criteria context must be specified.");
                }

                GetProductServiceRequest getProductsServiceRequest = new GetProductServiceRequest(
                    queryCriteria,
                    request.RequestContext.GetChannelConfiguration().DefaultLanguageId,
                    false /*FetchProductsAvailableInFuture*/,
                    request.QueryResultSettings,
                    queryCriteria.IsOnline);

                ProductSearchResultContainer result = request.RequestContext.Runtime.Execute <ProductSearchServiceResponse>(
                    getProductsServiceRequest,
                    request.RequestContext).ProductSearchResult;

                // populate the active variant product id if this was a search by item or barcode
                if ((!queryCriteria.ItemIds.IsNullOrEmpty() || !queryCriteria.Barcodes.IsNullOrEmpty()) && !queryCriteria.SkipVariantExpansion)
                {
                    SetActiveVariants(queryCriteria, result);
                }

                if (!queryCriteria.Refinement.IsNullOrEmpty())
                {
                    result.Results = ProductRefinement.RefineProducts(result.Results, queryCriteria.Refinement);
                }

                return(new ProductSearchServiceResponse(result));
            }
            private ProductSearchResultContainer AssembleProducts(
                GetProductPartsDataResponse productPartsResponse,
                ReadOnlyCollection <UnitOfMeasureConversion> unitOfMeasureOptionsDataSet,
                ReadOnlyCollection <KitDefinition> kitDefinitions,
                ReadOnlyCollection <KitComponent> kitComponentAndSubstituteList,
                ReadOnlyCollection <KitConfigToComponentAssociation> kitConfigToComponentAssociations,
                ReadOnlyCollection <KitComponent> parentKitsComponentInfo)
            {
                var productSearchResult = new ProductSearchResultContainer();

                var productParts = new Tuple <ReadOnlyCollection <ProductIdentity>, ReadOnlyCollection <ProductVariant>, ReadOnlyCollection <ProductRules>, ReadOnlyCollection <ProductAttributeSchemaEntry>, ReadOnlyCollection <ProductProperty>, ReadOnlyCollection <ProductCatalog>, ReadOnlyCollection <ProductCategoryAssociation>, Tuple <ReadOnlyCollection <RelatedProduct> > >(
                    productPartsResponse.ProductIdentities,
                    productPartsResponse.ProductVariants,
                    productPartsResponse.ProductRules,
                    productPartsResponse.ProductAttributeSchemaEntries,
                    productPartsResponse.ProductProperties,
                    productPartsResponse.ProductCatalogs,
                    productPartsResponse.CategoryAssociations,
                    new Tuple <ReadOnlyCollection <RelatedProduct> >(productPartsResponse.RelatedProducts));

                ProductBuilder.AssembleProductsFromDataSets(
                    this.request.Criteria.Context,
                    this.request.Criteria.DataLevel,
                    productParts,
                    productPartsResponse.LinkedProducts,
                    kitDefinitions,
                    kitComponentAndSubstituteList,
                    kitConfigToComponentAssociations,
                    parentKitsComponentInfo,
                    unitOfMeasureOptionsDataSet,
                    productSearchResult,
                    this.request.RequestContext.GetChannelConfiguration().ProductDefaultImageTemplate);

                return(productSearchResult);
            }
            private static ProductSearchRealtimeResponse GetRemoteProductsByKeyword(GetRemoteProductsByKeywordRealtimeRequest request)
            {
                var  client           = new TransactionServiceClient(request.RequestContext);
                long currentChannelId = request.RequestContext.GetPrincipal().ChannelId;

                var products = client.GetProductsByKeyword(
                    currentChannelId,
                    request.ChannelId,
                    request.CatalogId.GetValueOrDefault(),
                    request.Keywords,
                    request.QueryResultSettings.Paging.Skip,
                    request.QueryResultSettings.Paging.Top,
                    request.AttributeIds);

                var results = new ProductSearchResultContainer(products);

                return(new ProductSearchRealtimeResponse(results));
            }
Example #4
0
            /// <summary>
            /// Executes the workflow to retrieve products using the specified criteria.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override ProductSearchResponse Process(ProductSearchRequest request)
            {
                ThrowIf.Null(request, "request");

                if (request.QueryCriteria == null)
                {
                    throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, "The query criteria must be specified.");
                }

                if (request.QueryCriteria.Context == null || request.QueryCriteria.Context.ChannelId.HasValue == false)
                {
                    throw new DataValidationException(DataValidationErrors.Microsoft_Dynamics_Commerce_Runtime_RequiredValueNotFound, "The channel identifier on the query criteria context must be specified.");
                }

                long currentChannelId = this.Context.GetPrincipal().ChannelId;

                var searchRequest = new ProductSearchServiceRequest(request.QueryCriteria, request.QueryResultSettings);
                ProductSearchServiceResponse searchResponse = this.Context.Runtime.Execute <ProductSearchServiceResponse>(searchRequest, this.Context);

                ProductSearchResultContainer results = searchResponse.ProductSearchResult;

                if (results.Results.Any() && !request.QueryCriteria.Context.IsRemoteLookup(currentChannelId))
                {
                    // retrieve and update prices
                    var priceRequest  = new GetProductPricesServiceRequest(results.Results);
                    var priceResponse = this.Context.Execute <GetProductPricesServiceResponse>(priceRequest);
                    var productMap    = new Dictionary <long, Product>(results.Results.Count);

                    foreach (var product in results.Results)
                    {
                        productMap[product.RecordId] = product;
                    }

                    // update prices on the products
                    SearchProductsRequestHandler.SetProductPrices(priceResponse.ProductPrices.Results, productMap, results.ProductIdLookupMap);
                }

                return(new ProductSearchResponse(results));
            }
Example #5
0
            /// <summary>
            /// Set active variants.
            /// </summary>
            /// <param name="searchCriteria">The search criteria.</param>
            /// <param name="searchResult">The search result.</param>
            private static void SetActiveVariants(ProductSearchCriteria searchCriteria, ProductSearchResultContainer searchResult)
            {
                ThrowIf.Null(searchCriteria, "searchCriteria");
                ThrowIf.Null(searchResult, "searchResult");

                // Barcode searches are translated at the higher level into searches by item id. We'll ignore the barcode collection for now,
                // until we've added support for "native" retrieval of products by barcode.
                if (searchCriteria.ItemIds == null ||
                    searchCriteria.ItemIds.Count == 0 ||
                    searchResult.Results.Count == 0)
                {
                    return;
                }

                // It is possible that the search criteria includes several invent dim ids of the same item id. In that case we
                // pick the first one as the active variant, and the client will have to traverse the composition information to
                // do a proper matching. However, that case should be rare. The more typical case when this code path is being hit
                // is the scanning of a barcode. Since we don't support batching of barcode-based searches, there will be exactly
                // one variant being returned.
                Dictionary <string, string> itemIdLookup = new Dictionary <string, string>();

                // pre-populate the lookup map
                foreach (var entry in searchCriteria.ItemIds)
                {
                    // skip over empty invent dim ids
                    if (string.IsNullOrWhiteSpace(entry.InventDimensionId))
                    {
                        continue;
                    }

                    if (!itemIdLookup.ContainsKey(entry.ItemId))
                    {
                        itemIdLookup.Add(entry.ItemId, entry.InventDimensionId);
                    }
                }

                // walk the results, setting the active variant as needed.
                foreach (var product in searchResult.Results)
                {
                    // skip over non-masters or those whose item id was not part of the search criteria
                    if (!product.IsMasterProduct ||
                        !itemIdLookup.ContainsKey(product.ItemId))
                    {
                        continue;
                    }

                    product.CompositionInformation.VariantInformation.ActiveVariantProductId
                        = product.CompositionInformation.VariantInformation.IndexedInventoryDimensionIds[itemIdLookup[product.ItemId]];
                }
            }
Example #6
0
            private static ProductSearchServiceResponse SearchRemoteProducts(ProductSearchServiceRequest request)
            {
                ProductSearchResultContainer results = null;

                bool searchByProduct  = request.QueryCriteria.Ids != null && request.QueryCriteria.Ids.Any();
                bool searchByCategory = request.QueryCriteria.CategoryIds != null && request.QueryCriteria.CategoryIds.Any();
                bool searchByKeyword  = !string.IsNullOrEmpty(request.QueryCriteria.SearchCondition);

                if (searchByCategory && request.QueryCriteria.CategoryIds.HasMultiple())
                {
                    throw new NotSupportedException("Only a single category identifier can be specified when searching remotely.");
                }

                if (!(searchByProduct ^ searchByCategory ^ searchByKeyword))
                {
                    throw new NotSupportedException("When searching remotely you can search only by id, category or keyword.");
                }

                string attributeIds = string.Concat(RetailProductChannelProductAttributeId.ProductName, ",", RetailProductChannelProductAttributeId.Image);

                if (searchByProduct)
                {
                    GetProductDataRealtimeRequest getProductDataServiceRequest = new GetProductDataRealtimeRequest(request.QueryCriteria.Ids);
                    var getProductDataResponse = request.RequestContext.Execute <GetProductDataRealtimeResponse>(getProductDataServiceRequest);
                    var productsXml            = getProductDataResponse.ProductDataXml;

                    var manager = new ProductDataManager(request.RequestContext);
                    manager.SaveProductData(productsXml);

                    // Checks if the downloaded product contains a kit product and retrieve the component and substitutes of the kit product.
                    RetrieveComponentProducts(request, productsXml);

                    // Reset the channel context since the remote product data has been saved locally.
                    request.QueryCriteria.Context.ChannelId = request.RequestContext.GetPrincipal().ChannelId;
                    request.QueryCriteria.Context.CatalogId = 0;

                    results = SearchProductsLocal(request).ProductSearchResult;
                }
                else if (searchByCategory)
                {
                    var getProductsByCategoryRequest = new GetRemoteProductsByCategoryRealtimeRequest(
                        request.QueryCriteria.Context.ChannelId.Value,
                        request.QueryCriteria.Context.CatalogId,
                        request.QueryCriteria.CategoryIds.First(),
                        attributeIds,
                        request.QueryCriteria.IncludeProductsFromDescendantCategories);

                    getProductsByCategoryRequest.QueryResultSettings = request.QueryResultSettings;

                    var response = request.RequestContext.Execute <ProductSearchRealtimeResponse>(getProductsByCategoryRequest);
                    results = response.ProductSearchResult;
                }
                else if (searchByKeyword)
                {
                    var getProductsByKeywordRequest = new GetRemoteProductsByKeywordRealtimeRequest(
                        request.QueryCriteria.Context.ChannelId.Value,
                        request.QueryCriteria.Context.CatalogId,
                        request.QueryCriteria.SearchCondition,
                        attributeIds);

                    getProductsByKeywordRequest.QueryResultSettings = request.QueryResultSettings;

                    var response = request.RequestContext.Execute <ProductSearchRealtimeResponse>(getProductsByKeywordRequest);
                    results = response.ProductSearchResult;
                }

                return(new ProductSearchServiceResponse(results));
            }
            /// <summary>
            /// Get a tuple of item identifiers and inventory dimension identifiers for every product identifier.
            /// </summary>
            /// <param name="productIds">List of product identifiers.</param>
            /// <param name="productSearchResult">The search result from performing a SearchProducts on the product identifiers.</param>
            /// <param name="productMap">A mapping of product identifiers to the respective product object.</param>
            /// <returns>List of item identifiers, inventory dimension identifiers and unit of measures.</returns>
            private IEnumerable <Tuple <string, string, string> > GetItemAndInventDimId(IEnumerable <long> productIds, ProductSearchResultContainer productSearchResult, Dictionary <long, Product> productMap)
            {
                var items = new HashSet <Tuple <string, string, string> >();

                foreach (var requestedProductId in productIds)
                {
                    long           productId;
                    Product        product;
                    ProductVariant variant;

                    if (!productSearchResult.ProductIdLookupMap.TryGetValue(requestedProductId, out productId))
                    {
                        continue;
                    }

                    if (!productMap.TryGetValue(productId, out product))
                    {
                        continue;
                    }

                    if (product.IsMasterProduct &&
                        product.RecordId != requestedProductId &&
                        product.TryGetVariant(requestedProductId, out variant))
                    {
                        // Product is a variant.
                        items.Add(new Tuple <string, string, string>(variant.ItemId, variant.InventoryDimensionId, product.Rules.DefaultUnitOfMeasure));
                    }
                    else if (product.IsMasterProduct)
                    {
                        // Product is a master, so quantities of all it's variants will be retrieved.
                        var variants = product.GetVariants();
                        foreach (var item in variants)
                        {
                            items.Add(new Tuple <string, string, string>(item.ItemId, item.InventoryDimensionId, product.Rules.DefaultUnitOfMeasure));
                        }
                    }
                    else
                    {
                        // Product is a standalone. Hence, InventDimId is set to empty.
                        items.Add(new Tuple <string, string, string>(product.ItemId, string.Empty, product.Rules.DefaultUnitOfMeasure));
                    }
                }

                return(items);
            }
            /// <summary>
            /// Compiles results by preventing duplicates and calculating sum of quantity for master products.
            /// </summary>
            /// <param name="quantities">List of ProductAvailableQuantity.</param>
            /// <param name="productIds">List of product identifiers.</param>
            /// <param name="productSearchResult">The search result from performing a SearchProducts on the product ids.</param>
            /// <param name="productMap">A mapping of product identifiers to the respective product object.</param>
            /// <returns>List of ProductAvailableQuantity mapped to the appropriate product identifier.</returns>
            private IEnumerable <ProductAvailableQuantity> ProcessItemAvailabilities(IEnumerable <ItemAvailability> quantities, IEnumerable <long> productIds, ProductSearchResultContainer productSearchResult, Dictionary <long, Product> productMap)
            {
                var productAvailableQuantities = new HashSet <ProductAvailableQuantity>();

                foreach (var requestedProductId in productIds)
                {
                    long                     productId;
                    Product                  product;
                    ProductVariant           variant;
                    ProductAvailableQuantity productAvailableQuantity;

                    if (!productSearchResult.ProductIdLookupMap.TryGetValue(requestedProductId, out productId))
                    {
                        continue;
                    }

                    if (!productMap.TryGetValue(productId, out product))
                    {
                        continue;
                    }

                    if (product.IsMasterProduct &&
                        product.RecordId != requestedProductId &&
                        product.TryGetVariant(requestedProductId, out variant))
                    {
                        // Product is a variant.
                        var quantity = quantities.Where(i => i.ItemId.Equals(variant.ItemId, StringComparison.OrdinalIgnoreCase) && i.VariantInventoryDimensionId.Equals(variant.InventoryDimensionId, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
                        productAvailableQuantity = new ProductAvailableQuantity()
                        {
                            ProductId         = requestedProductId,
                            AvailableQuantity = quantity != null ? quantity.AvailableQuantity : 0m,
                            UnitOfMeasure     = product.Rules.DefaultUnitOfMeasure,
                        };
                    }
                    else if (product.IsMasterProduct)
                    {
                        // Product is a master, so quantities of all it's variants will be retrieved.
                        var     variants      = quantities.Where(i => i.ItemId.Equals(product.ItemId, StringComparison.OrdinalIgnoreCase));
                        decimal totalQuantity = variants.Sum(i => i.AvailableQuantity);
                        productAvailableQuantity = new ProductAvailableQuantity
                        {
                            ProductId         = product.RecordId,
                            AvailableQuantity = totalQuantity,
                            UnitOfMeasure     = product.Rules.DefaultUnitOfMeasure,
                        };
                    }
                    else
                    {
                        // Product is a standalone. Hence, InventDimId is set to empty.
                        var quantity = quantities.Where(i => i.ItemId.Equals(product.ItemId, StringComparison.OrdinalIgnoreCase) && string.IsNullOrEmpty(i.VariantInventoryDimensionId)).SingleOrDefault();
                        productAvailableQuantity = new ProductAvailableQuantity
                        {
                            ProductId         = product.RecordId,
                            AvailableQuantity = quantity != null ? quantity.AvailableQuantity : 0m,
                            UnitOfMeasure     = product.Rules.DefaultUnitOfMeasure,
                        };
                    }

                    productAvailableQuantities.Add(productAvailableQuantity);
                }

                return(productAvailableQuantities);
            }
Example #9
0
            /// <summary>
            /// Executes the workflow to retrieve the prices and calculated discount amount for the given product identifiers.
            /// </summary>
            /// <param name="request">The request.</param>
            /// <returns>The response.</returns>
            protected override GetIndependentProductPriceDiscountResponse Process(GetIndependentProductPriceDiscountRequest request)
            {
                ThrowIf.Null(request, "request");

                ProductSearchCriteria productSearchCriteria = new ProductSearchCriteria(
                    request.Context.ChannelId.GetValueOrDefault(),
                    request.Context.CatalogId.GetValueOrDefault())
                {
                    DataLevel = CommerceEntityDataLevel.Standard,
                    Ids       = request.ProductIds.ToList()
                };

                ProductSearchResultContainer productSearchResult = request.RequestContext.Runtime.Execute <ProductSearchServiceResponse>(
                    new ProductSearchServiceRequest(productSearchCriteria, request.QueryResultSettings), request.RequestContext).ProductSearchResult;

                List <ProductPrice> productPrices = new List <ProductPrice>(request.ProductIds.Count());

                // Create sales line for every product id there is in the request.
                List <SalesLine> salesLines = new List <SalesLine>(request.ProductIds.Count());

                foreach (var product in productSearchResult.Results)
                {
                    if (product.IsMasterProduct)
                    {
                        foreach (var variant in product.GetVariants())
                        {
                            if (request.ProductIds.Contains(variant.DistinctProductVariantId))
                            {
                                salesLines.Add(new SalesLine
                                {
                                    ItemId  = product.ItemId,
                                    Variant = variant,
                                    InventoryDimensionId    = variant.InventoryDimensionId,
                                    SalesOrderUnitOfMeasure = product.Rules.DefaultUnitOfMeasure,
                                    LineId    = System.Guid.NewGuid().ToString("N"),
                                    Quantity  = 1,
                                    ProductId = variant.DistinctProductVariantId,
                                    CatalogId = request.Context.CatalogId.GetValueOrDefault()
                                });
                            }
                        }
                    }
                    else
                    {
                        salesLines.Add(new SalesLine
                        {
                            ItemId = product.ItemId,
                            SalesOrderUnitOfMeasure = product.Rules.DefaultUnitOfMeasure,
                            LineId    = System.Guid.NewGuid().ToString("N"),
                            Quantity  = 1,
                            ProductId = product.RecordId,
                            CatalogId = request.Context.CatalogId.GetValueOrDefault()
                        });
                    }
                }

                // Set the catalog ids on the sales lines.
                if (request.Context.CatalogId != null)
                {
                    if (request.Context.CatalogId.Value > 0)
                    {
                        // If a specific catalogId is set on the context, add it to the catalogIds on the sales lines.
                        foreach (var sl in salesLines)
                        {
                            sl.CatalogIds.Add(request.Context.CatalogId.Value);
                        }
                    }
                    else
                    {
                        GetProductCatalogAssociationsDataRequest productCatalogAssociationRequest = new GetProductCatalogAssociationsDataRequest(salesLines.Select(p => p.ProductId))
                        {
                            QueryResultSettings = QueryResultSettings.AllRecords
                        };
                        ReadOnlyCollection <ProductCatalogAssociation> productCatalogs = request.RequestContext.Runtime.Execute <GetProductCatalogAssociationsDataResponse>(
                            productCatalogAssociationRequest,
                            request.RequestContext).CatalogAssociations;

                        // If catalogId is 0, add all independent catalogs to the catalogIds on the sales lines.
                        foreach (var sl in salesLines)
                        {
                            sl.CatalogIds.UnionWith(productCatalogs.Where(pc => pc.ProductRecordId == sl.ProductId).Select(pc => pc.CatalogRecordId));
                        }
                    }
                }

                Collection <SalesAffiliationLoyaltyTier> affiliations = null;

                if (request.AffiliationLoyaltyTiers != null)
                {
                    affiliations = new Collection <SalesAffiliationLoyaltyTier>((from alt in request.AffiliationLoyaltyTiers
                                                                                 select new SalesAffiliationLoyaltyTier
                    {
                        AffiliationId = alt.AffiliationId,
                        AffiliationType = alt.AffiliationType,
                        LoyaltyTierId = alt.LoyaltyTierId,
                        ReasonCodeLines = alt.ReasonCodeLines,
                        CustomerId = alt.CustomerId,
                        ChannelId = request.Context.ChannelId.GetValueOrDefault()
                    }).ToList());
                }

                SalesTransaction transaction = new SalesTransaction
                {
                    SalesLines = new Collection <SalesLine>(salesLines),
                    CustomerId = request.CustomerAccountNumber,
                    AffiliationLoyaltyTierLines = affiliations
                };

                // Calculate prices and discounts for sales lines
                GetIndependentPriceDiscountServiceRequest itemPriceDiscountServiceRequest = new GetIndependentPriceDiscountServiceRequest(transaction);
                GetPriceServiceResponse      itemPriceServiceResponse = this.Context.Execute <GetPriceServiceResponse>(itemPriceDiscountServiceRequest);
                Dictionary <long, SalesLine> salesLineDictionary      = itemPriceServiceResponse.Transaction.SalesLines.ToDictionary(sl => sl.ProductId);

                foreach (long productId in request.ProductIds)
                {
                    SalesLine salesLine;

                    if (!salesLineDictionary.TryGetValue(productId, out salesLine))
                    {
                        salesLine = new SalesLine();
                    }

                    ProductPrice productPrice = new ProductPrice
                    {
                        UnitOfMeasure        = salesLine.SalesOrderUnitOfMeasure,
                        ItemId               = salesLine.ItemId,
                        InventoryDimensionId = salesLine.InventoryDimensionId,
                        BasePrice            = salesLine.BasePrice,
                        TradeAgreementPrice  = salesLine.AgreementPrice,
                        AdjustedPrice        = salesLine.AdjustedPrice,
                        DiscountAmount       = salesLine.DiscountAmount,
                        ProductId            = productId,
                        ChannelId            = request.Context.ChannelId.GetValueOrDefault(),
                        CatalogId            = request.Context.CatalogId.GetValueOrDefault()
                    };

                    productPrices.Add(productPrice);
                }

                return(new GetIndependentProductPriceDiscountResponse(productPrices.AsPagedResult()));
            }
            /// <summary>
            /// Get products based on the <see cref="request" />.
            /// </summary>
            /// <returns>
            /// The <see cref="ProductSearchResultContainer" /> containing the requested products.
            /// </returns>
            public ProductSearchResultContainer GetProducts()
            {
                ProductL2CacheDataStoreAccessor level2CacheDataAccessor = (ProductL2CacheDataStoreAccessor)this.DataStoreManagerInstance.RegisteredAccessors[DataStoreType.L2Cache];

                List <long> missed = new List <long>(0);

                ProductSearchResultContainer result = new ProductSearchResultContainer();
                bool useSearchById             = !this.request.Criteria.Ids.IsNullOrEmpty();
                ProductSearchCriteria criteria = this.request.Criteria;

                PagingInfo originalPagingInfo = this.request.QueryResultSettings.Paging;

                if (useSearchById)
                {
                    // Do not read from cache if SkipVariantExpansion is true
                    // This is because entry point for the cache item is a Product and not variant,
                    // therefore we would not be able to distinguish between cases when I read product by master or its variants
                    // (where all othere parameters to the search are equal) where SkipVariants is set to true.
                    if (!criteria.SkipVariantExpansion)
                    {
                        result = level2CacheDataAccessor.GetProductsByIds(criteria.Ids, criteria, out missed);

                        // we have partial results.
                        if (missed.Count > 0)
                        {
                            // modify the criteria to include only the missed ids.
                            criteria     = this.request.Criteria.Clone();
                            criteria.Ids = missed;

                            // we need to return all missed from the db since the pagination was already done on the product ids above.
                            this.request.QueryResultSettings.Paging = PagingInfo.AllRecords;
                        }
                        else
                        {
                            // just return the result; all the products were found in the cache.
                            return(result);
                        }
                    }
                }

                // regardless if the search was done
                ProductSearchResultContainer nonCachedResults = this.GetProductsFromDbStorage(criteria);

                if (nonCachedResults.Results != null)
                {
                    // Do not store in cache if SkipVariantExpansion is true
                    // This is because entry point for the cache item is a Product and not variant,
                    // therefore we would not be able to distinguish between cases when I read product by master or its variants
                    // (where all othere parameters to the search are equal) where SkipVariants is set to true. In this case
                    // each next entry will overwrite previous.
                    if (!criteria.SkipVariantExpansion)
                    {
                        level2CacheDataAccessor.PutProducts(nonCachedResults, criteria);
                    }

                    List <Product> fullList = new List <Product>(0);
                    if (result.Results != null)
                    {
                        fullList.AddRange(result.Results);
                    }

                    fullList.AddRange(nonCachedResults.Results);
                    result = new ProductSearchResultContainer(fullList.AsReadOnly());
                }

                if (useSearchById)
                {
                    this.request.QueryResultSettings.Paging = originalPagingInfo;
                }

                return(result);
            }