Loading...
Area: Episerver Commerce
Applies to versions: 10 and higher
Other versions:

Indexing variants in a product document

Recommendations [hide]

This topic explains how to index a variant for a product document in a solution using the Episerver Search & Navigation-Commerce integration (EPiServer.Find.Commerce). The topic also provides examples of including and excluding variants, and adding price and inventories to a product document to be indexed. 

Indexing the entire variant inside the product

By default, variants are indexed as ContentReferences inside the product document sent to the 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.

Overriding CatalogContentClientConventions

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
  {
  }

Excluding 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());
      }
  }

Including 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());
      }
  }

Adding 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());
      }
  }

Adding 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());
      }
  }

Excluding 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());
      }
  }

Listening 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);
      }
  }
Do you find this information helpful? Please log in to provide feedback.

Last updated: Oct 24, 2016

Recommendations [hide]