Hide menu Last updated: Nov 03 2015

Indexing variations in a product document

This topic explains how to index a variation (variant) for a product document in a solution using the Find search provider, EPiServer.Find.Commerce. The article also provides examples of including and excluding variations, and adding price and inventories to a product document to be indexed.

Indexing the entire variation inside the product

By default, variations are indexed as ContentReferences inside the product document sent to the Find index. This allows all variation references for a specific product from the index and gets the product for a specific variation.

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

Subtopics

Overriding CatalogContentClientConventions
Excluding variation reference indexing for product content
Including related variation content items in product content
Adding default price and prices in product content
Adding inventory in product content
Excluding indexing of variation content
Listening for price and inventory changes

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.Container.Configure(config =>
            {
                config.For<CatalogContentClientConventions>().Use<SiteCatalogContentClientConventions>();
            });
        }
    }

    public class SiteCatalogContentClientConventions : CatalogContentClientConventions
    {
    }

Excluding variation reference indexing for product content

To exclude the variation 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 variation content items in product content

Create a new extension method for product content to be able to index variations 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 variations
  • index prices from variations 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 variations.

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 variation content

Exclude the variation 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.Container.Configure(config => { config.For<CatalogContentEventListener>().Singleton().Use<SiteCatalogContentEventListener>(); }); } } public class SiteCatalogContentEventListener : CatalogContentEventListener {
private ReferenceConverter _referenceConverter; private readonly IContentRepository _contentRepository; private readonly IRelationRepository _relationRepository; public SiteCatalogEventListener(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 contentLinks, IDictionary<Type, bool> cachedReindexContentOnEventForType, Func isReindexingContentOnUpdates) { // Update parent contents var contents = _contentRepository.GetItems(contentLinks, CultureInfo.InvariantCulture).ToList(); var parentContentLinks = new List(); 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 variations still needed, keep the line below // IndexContentsIfNeeded(contentLinks, GetIndexContentAction());
IndexContentsIfNeeded(parentContentLinks, GetIndexContentAction()); } }

Comments

Is this document quite out of date? 

In the latest version (Find Commerce 9.5), I can only find CatalogContentEventListener, there is no CatalogKeyEventListener class anymore.

In addition, the IndexContentsIfNeeded is no longer virtual method, I can't find the proper one to override. 

Can anyone please update it?

Thanks for the comment. We are updating the documentation, and adding back the virtual method. Bugs COM-1366.

Can you guys please review this entire document?

The sample code is either not compile or display obsolete method being used.

Thanks,

Vincent

Any feeback on this documentation? When we can expect an update?