
In this previous Blog Post, we discussed how we built a Global SXA Search Feature that combines both Commerce Products and other types of Content.
During this implementation we faced some design challenges. In this blog post I would like to share one of them and how we managed to work around it.
If you've worked with Sitecore Commerce previously you know there is a Read-Only Catalog Provider that converts Sellable Items to Sitecore Catalog Items in the Content Tree.
These items, unlike most of content in the Content Tree, are not stored in master or web database, but loaded during runtime.
These Catalog items are however stored in Master and Web Indexes and used for Search.
The duplicate products issue:
The duplicate issue is caused by the 1 to N relationship between a Sitecore Commerce Sellable Item and the resulting Sitecore Product in the content tree.
In fact, the same product can show as many times as it is associated with different categories.
For example: If we have book in Sale this week. Chances are this book will be associated with 'Books' category and 'Sale' category.
In the content tree, the catalog data provider will generate 2 items for this same product and both will end up indexed and showing in Search Results.
The solution:
After exploring many options, we ended up going with the solution explained below:
1) Create a new 'AllProducts' Category:
This category will be associated to all the sellable items from the catalog.
Place this category outside of the root 'Shop' Category to make sure it's not visible in the Store.
2) Create a One-Time job/endpoint to populate this category with all sellable items from the catalog.
3) Maintain and ensure this category is up to date with all products at all times by adding custom blocks to IAssociateSellableItemToParentPipeline and IDeleteRelationshipPipeline
4) Customize the SXA Search query by adding a filter to only return products from this new category. Example:
var allProductsCategoryPredicate = PredicateBuilder.True<ContentPage>();
allProductsCategoryPredicate = allProductsCategoryPredicate.And(i => i.TemplateId != new ID(Foundation.Common.Constants.Templates.CommerceProduct))
.Or(i => i.Parent == allProductsCategoryId);
The plugin:
You can find a plugin here with the solution listed above including:
The 'PopulateAllProductsCategory' Endpoint to populate the new Category with all the products from the catalog.
AddProductToAllProductsCategory Block to be added to IAssociateSellableItemToParentPipeline to ensure any added product will be included in the AllProducts category.
RemoveProductFromAllProductsCategory Block to be added to IDeleteRelationshipPipeline to make sure if a product is removed from all the categories that it is also removed from AllProducts category.
How to use the plugin:
Clone the plugin from this Github repo
Add the Plugin as a project reference to your Sitecore Commerce Engine.
To run the one time endpoint from Postman use this Url: {{ServiceHost}}/{{ShopsApi}}/PopulateAllProductsCategory()
I hope you find this Post and Plugin helpful.
Feel free to leave comments or questions.
Comments