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 Connect 13.
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
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);
}
}
Updated 3 months ago