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());
}
}
Last updated: Nov 03, 2015