HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket

Index variants in a product document

Describes how to index a variant, include and exclude variants, and add price and inventories to a product document to be indexed in Optimizely Commerce (PaaS).

Index the entire variant inside the product

By default, variants are indexed as ContentReferences inside the product document sent to the Optimizely Search & Navigation index. This allows all variant references for a specific product from the index and gets the product for a specific variant.

Depending on how your site works, it might be better to index the entire variant inside of the product, as shown in the following examples.

Override CatalogContentClientConventions

[New in Commerce (PaaS) 14]

Override the CatalogContentClientConventions class, and register it in the ConfigureServices method of startup.cs.

//Startup class
    public void ConfigureServices(IServiceCollection services)
    {
            services.AddTransient<CatalogContentClientConventions, SiteCatalogContentClientConventions>();
    }

Versions 10-13

Override the CatalogContentClientConventions class, and register it in an initialization module to override the default conventions.

[InitializableModule]
    [ModuleDependency(typeof(FindCommerceInitializationModule))]
    public class InitializationModule : IConfigurableModule
      {
        public void Initialize(InitializationEngine context)
          {
          }
        public void Uninitialize(InitializationEngine context)
          {
          }
        public void ConfigureContainer(ServiceConfigurationContext context)
          {
            context.Services.AddTransient<CatalogContentClientConventions, SiteCatalogContentClientConventions>();
          }
      }
    public class SiteCatalogContentClientConventions : CatalogContentClientConventions
      {
      }

Exclude variant reference indexing for product content

To exclude variant references, override the ApplyProductContentConventions, and exclude the field using conventions.

public class SiteCatalogContentClientConventions : CatalogContentClientConventions
      {
        protected override void ApplyProductContentConventions(TypeConventionBuilder<ProductContent> conventionBuilder)
          {
            base.ApplyProductContentConventions(conventionBuilder);
            conventionBuilder
              .ExcludeField(x => x.Variations());
          }
      }

Include related variant content items in product content

Create a new extension method for product content to be able to index variants for it.

public static class ProductContentExtensions
      {
        public static IEnumerable<VariationContent> VariationContents(this ProductContent productContent)
          {
            return VariationContents(productContent, ServiceLocator.Current.GetInstance<IContentLoader>(), ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
        public static IEnumerable<VariationContent> VariationContents(this ProductContent productContent, IContentLoader contentLoader, IRelationRepository relationRepository)
          {
            return contentLoader.GetItems(productContent.GetVariants(relationRepository), productContent.Language).OfType<VariationContent>();
          }
      }
    
    public class SiteCatalogContentClientConventions : CatalogContentClientConventions
      {
        protected override void ApplyProductContentConventions(TypeConventionBuilder<ProductContent> conventionBuilder)
          {
            base.ApplyProductContentConventions(conventionBuilder);
            conventionBuilder
              .ExcludeField(x => x.Variations())
              .IncludeField(x => x.VariationContents());
          }
      }

Add default price and prices in product content

The following example shows how to

  • index the highest default price for product variants
  • index prices from variants in the Prices extension method
public static class ProductContentExtensions
      {
        public static Price DefaultPrice(this ProductContent productContent)
          {
            return DefaultPrice(productContent, ServiceLocator.Current.GetInstance<ReadOnlyPricingLoader>(), ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
    
        public static Price DefaultPrice(this ProductContent productContent, ReadOnlyPricingLoader pricingLoader, IRelationRepository relationRepository)
          {
            var maxPrice = new Price();
            var variationLinks = productContent.GetVariants(relationRepository);
            foreach (var variationLink in variationLinks)
              {
                var defaultPrice = pricingLoader.GetDefaultPrice(variationLink);
                if (defaultPrice.UnitPrice.Amount > maxPrice.UnitPrice.Amount)
                  {
                    maxPrice = defaultPrice;
                  }
              }
            return maxPrice;
          }
    
        public static IEnumerable<Price> Prices(this ProductContent productContent)
          {
            return Prices(productContent, ServiceLocator.Current.GetInstance<ReadOnlyPricingLoader>(), ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
    
        public static IEnumerable<Price> Prices(this ProductContent productContent, ReadOnlyPricingLoader pricingLoader, IRelationRepository relationRepository)
          {
            var variationLinks = productContent.GetVariants(relationRepository);
            return variationLinks.SelectMany(variationLink => pricingLoader.GetPrices(variationLink, null, Enumerable.Empty<CustomerPricing>()));
          }
    
        public static IEnumerable<VariationContent> VariationContents(this ProductContent productContent)
          {
            return VariationContents(productContent, ServiceLocator.Current.GetInstance<IContentLoader>(), ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
    
        public static IEnumerable<VariationContent> VariationContents(this ProductContent productContent, IContentLoader contentLoader, IRelationRepository relationRepository)
          {
            return contentLoader.GetItems(productContent.GetVariants(relationRepository), productContent.Language).OfType<VariationContent>();
          }
      }
    
    public class SiteCatalogContentClientConventions : CatalogContentClientConventions
      {
        protected override void ApplyProductContentConventions(TypeConventionBuilder<ProductContent> conventionBuilder)
          {
            base.ApplyProductContentConventions(conventionBuilder);
            conventionBuilder
              .ExcludeField(x => x.Variations())
              .IncludeField(x => x.VariationContents())
              .IncludeField(x => x.DefaultPrice())
              .IncludeField(x => x.Prices());
          }
      }

Add inventory in product content

The following example shows how to index the inventories from variants.

public static class ProductContentExtensions
      {
        public static IEnumerable Inventories(this ProductContent productContent)
          {
            return Inventories(
              productContent,
              ServiceLocator.Current.GetInstance<IContentLoader>(),
              ServiceLocator.Current.GetInstance<InventoryLoader>(),
              ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
    
        public static IEnumerable<Inventory> Inventories(this ProductContent productContent, IContentLoader contentLoader, InventoryLoader inventoryLoader, IRelationRepository relationRepository)
          {
            var variations = contentLoader.GetItems(productContent.GetVariants(relationRepository), productContent.Language).OfType<VariationContent>();
            return variations.SelectMany(x => x.GetStockPlacement(inventoryLoader));
          }
    
        public static Price DefaultPrice(this ProductContent productContent)
          {
            return DefaultPrice(productContent, ServiceLocator.Current.GetInstance<ReadOnlyPricingLoader>(), ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
    
        public static Price DefaultPrice(this ProductContent productContent, ReadOnlyPricingLoader pricingLoader, IRelationRepository relationRepository)
          {
            var maxPrice = new Price();
            var variationLinks = productContent.GetVariants(relationRepository);
            foreach (var variationLink in variationLinks)
              {
                var defaultPrice = pricingLoader.GetDefaultPrice(variationLink);
                if (defaultPrice.UnitPrice.Amount > maxPrice.UnitPrice.Amount)
                  {
                    maxPrice = defaultPrice;
                  }
              }
            return maxPrice;
          }
    
        public static IEnumerable<Price> Prices(this ProductContent productContent)
          {
            return Prices(productContent, ServiceLocator.Current.GetInstance<ReadOnlyPricingLoader>(), ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
    
        public static IEnumerable<Price> Prices(this ProductContent productContent, ReadOnlyPricingLoader pricingLoader, IRelationRepository relationRepository)
          {
            var variationLinks = productContent.GetVariants(relationRepository);
            return variationLinks.SelectMany(variationLink => pricingLoader.GetPrices(variationLink, null, Enumerable.Empty<CustomerPricing>()));
          }
    
        public static IEnumerable<VariationContent> VariationContents(this ProductContent productContent)
          {
            return VariationContents(productContent, ServiceLocator.Current.GetInstance<IContentLoader>(), ServiceLocator.Current.GetInstance<IRelationRepository>());
          }
    
        public static IEnumerable<VariationContent> VariationContents(this ProductContent productContent, IContentLoader contentLoader, IRelationRepository relationRepository)
          {
            return contentLoader.GetItems(productContent.GetVariants(relationRepository), productContent.Language).OfType<VariationContent>();
          }
      }
    
    public class SiteCatalogContentClientConventions : CatalogContentClientConventions
      {
        protected override void ApplyProductContentConventions(TypeConventionBuilder<ProductContent> conventionBuilder)
          {
            base.ApplyProductContentConventions(conventionBuilder);
            conventionBuilder
              .ExcludeField(x => x.Variations())
              .IncludeField(x => x.VariationContents())
              .IncludeField(x => x.DefaultPrice())
              .IncludeField(x => x.Prices())
              .IncludeField(x => x.Inventories());
          }
      }

Exclude indexing of variant content

Exclude the variant content from being indexed by calling ShouldIndex for the type, using the conventions API.

public class SiteCatalogContentClientConventions : CatalogContentClientConventions
      {
        protected override void ApplyProductContentConventions(ClientConventions.TypeConventionBuilder<ProductContent> conventionBuilder)
          {
            base.ApplyProductContentConventions(conventionBuilder);
            conventionBuilder
              .ExcludeField(x => x.Variations())
              .IncludeField(x => x.VariationContents())
              .IncludeField(x => x.DefaultPrice())
              .IncludeField(x => x.Prices())
              .IncludeField(x => x.Inventories());
          }
    
       public override void ApplyConventions(IClientConventions clientConventions)
          {
            base.ApplyConventions(clientConventions);
            // Uncomment line below if we don't index VariationContent
            // ContentIndexer.Instance.Conventions.ForInstancesOf<VariationContent>().ShouldIndex(x => false);
            SearchClient.Instance.Conventions.NestedConventions.ForInstancesOf<ProductContent>().Add(x => x.VariationContents());
          }
      }

Listen for price and inventory changes

The CatalogContentEventListener class listens for price changes for classes that implement IPricing, and inventories for classes that implement IStockPlacement. VariationContent implements both of them, but ProductContent does not implement them. Therefore, override the default implementation and make some changes.

[InitializableModule]
    [ModuleDependency(typeof(FindCommerceInitializationModule))]
    public class InitializationModule : IConfigurableModule
      {
        public void Initialize(InitializationEngine context)
          {
            context.Locate.Advanced.GetInstance<PriceIndexing>().IsIndexingIIndexedPrices = true;
          }
        public void Uninitialize(InitializationEngine context)
          {
          }
        public void ConfigureContainer(ServiceConfigurationContext context)
          {
            context.Services.AddSingleton<CatalogContentClientConventions, 
              SiteCatalogContentClientConventions>();
          }
      }
    
    public class SiteCatalogContentEventListener : CatalogContentEventListener
      {
        private ReferenceConverter _referenceConverter;
        private readonly IContentRepository _contentRepository;
        private readonly IRelationRepository _relationRepository;
    
        public SiteCatalogContentEventListener(
          ReferenceConverter referenceConverter,
          IContentRepository contentRepository,
          IClient client,
          CatalogEventIndexer indexer,
          CatalogContentClientConventions catalogContentClientConventions,
          PriceIndexing priceIndexing, 
          IRelationRepository relationRepository)
          : base(
                  referenceConverter,
                  contentRepository,
                  client,
                  indexer, 
                  catalogContentClientConventions,
                  priceIndexing)
          {
            _referenceConverter = referenceConverter;
            _contentRepository = contentRepository;
            _relationRepository = relationRepository;
          }
        protected override void IndexContentsIfNeeded(IEnumerable<ContentRefenrence> contentLinks, IDictionary<Type, bool> cachedReindexContentOnEventForType, Func<bool> isReindexingContentOnUpdates)
          {
            // Update parent contents
            var contents = _contentRepository.GetItems(contentLinks, CultureInfo.InvariantCulture).ToList();
            var parentContentLinks = new List<ContentReference>();
            foreach (var parents in contents.OfType().Select(content => _contentRepository.GetItems(content.GetParentProducts(_relationRepository), CultureInfo.InvariantCulture).Select(c => c.ContentLink).ToList()))
              {
                parentContentLinks.AddRange(parents);
              }
              // If index variants still needed, keep the line below
              // IndexContentsIfNeeded(contentLinks, GetIndexContentAction(), cachedReindexContentOnEventForType, isReindexingContentOnUpdates);
    
              IndexContentsIfNeeded(parentContentLinks, GetIndexContentAction(), cachedReindexContentOnEventForType, isReindexingContentOnUpdates);
          }
      }