Unwanted Lucene Search Results in EpiServer Commerce R2

Issue: User was getting unexpected search results for products, e.g. with a search key word ‘Sate’ he was getting same result as for ‘Site’. Although for most of the clients they will happy with these results but this particular client was unhappy with this result due to their business requirements. We do not have any word like ‘Sate’ in our Database or in Index file for Lucene. Clearly it was Fuzzy search that was causing this although in code we have set FuzzySearch = false;

Reason:
// Perform Lucene search
SearchResults results = searchFilterHelper.SearchEntries(criteria) as SearchResults;
In the Mediachase.Commerce.Website, Version=5.2.243.2, SearchFilterHelper’s SearchEntries() method, below:
public virtual ISearchResults SearchEntries(CatalogEntrySearchCriteria criteria) {  try  {
  _Results = Manager.Search(criteria);
 }
 catch (SystemException)
 {
  if (HttpContext.Current.IsDebuggingEnabled)
   throw;
 }
 // Perform fuzzy search if nothing has been found 
if (_Results.TotalCount == 0)  {
  criteria.IsFuzzySearch = true;
  criteria.FuzzyMinSimilarity = 0.7f;
  _Results = Manager.Search(criteria);
 }
 return _Results;
}
As you can see there is an initial call to Manager.Search().
Before returning, the TotalCount property on the ISearchResults object returned is checked to see if there were no results.
If there are no results a fuzzy search is done.

That was causing the issue although iSFuzzySearch was false at the time of request.

Fix: A fix is present in Episerver Commerce R2 Sp2. There is an extra Function is available that can be used to stop FuzzySearch if no result is found with actual keyword.
public virtual ISearchResults SearchEntries(CatalogEntrySearchCriteria criteria, bool isFuzzySearch, float minSimilarity)
// Perform Lucene search
SearchResults results = searchFilterHelper.SearchEntries(criteria,false,0) as SearchResults;

Special thanks to Jeff from EpiServer’s Developer Support team to find out the reason and fix.

Example Code:
internal CatalogIndexSearchDataSource CreateDataSource(SearchFilterHelper filter, int pageNo, int pageSize, ref int totalRows, bool cacheResults, int cacheTime, string nodeString, string keywords, List<string> metaClasses)
        {
            var currentCulture = Thread.CurrentThread.CurrentUICulture;
            Thread.CurrentThread.CurrentUICulture = new CultureInfo(SiteContext.Current.LanguageName);
            var recordsToRetrieve = pageSize;
            //cache timeout
            TimeSpan cacheTimeout = new TimeSpan(0, (int)cacheTime, 0);
            // Perform search
            SearchSort sortObject = null;
            // Put default sort order if none is set
            if (sortObject == null)
                sortObject = CatalogEntrySearchCriteria.DefaultSortOrder;
            var criteria = filter.CreateSearchCriteria(keywords, sortObject);
            //If meta classes are given to search.
            if (metaClasses != null)
                foreach (string metaClass in metaClasses)
                    criteria.SearchIndex.Add(metaClass);
           
            //Add catalogs
            foreach (var row in CatalogContext.Current.GetCatalogDto(SiteContext.Current.SiteId).Catalog)
            {
                if (row.IsActive && row.StartDate <= FrameworkContext.Current.CurrentDateTime && row.EndDate >= FrameworkContext.Current.CurrentDateTime)
                    criteria.CatalogNames.Add(row.Name);
            }
            //add catalog nodes
            if (!string.IsNullOrEmpty(nodeString))
            {
                foreach (string outline in SearchFilterHelper.GetOutlinesForNode(nodeString))
                {
                    criteria.Outlines.Add(outline);
                }               
            }
            CatalogIndexSearchDataSource dataSource = null;
            // No need to perform search if no catalogs specified
            if (criteria.CatalogNames.Count != 0)
            {
                Entries entries = new Entries();
            // Perform Lucene search
            SearchResults results = searchFilterHelper.SearchEntries(criteria,false,0) as SearchResults;
           
            // Get IDs we need
            int[] resultIndexes = results.GetIntResults(pageNumber , maxRows+5); // we add padding here to accomodate entries that might have been deleted since last indexing
            if (resultIndexes.Length > 0)
            {
                int[] productResultIndexes = RefinementHelper.GetParentProducts(pageSize, pageNumber, resultIndexes, ref totalRows);
                // Retrieve actual entry objects, with no caching
                entries = CatalogContext.Current.GetCatalogEntries(productResultIndexes, false, new TimeSpan(), responseGroup);
                // Add in attribute information
                AddAttributes(entries, results);
                // Insert to the cache collection
                entries.TotalResults = totalRows;
                CatalogCache.Insert(cacheKey, entries, cacheTimeout);                dataSource = new CatalogIndexSearchDataSource { TotalResults = totalRows, CatalogEntries = entries };
            }
            Thread.CurrentThread.CurrentUICulture = currentCulture;
            return dataSource;
        }

Clone a purchase order and convert into cart

We have a requirement for our client that he can masquerade user and can create a new cart based on some existing Purchase Order(e.g. for damaged/lost orders).

Option 1: Get The OrderForm and other objects from PurchaseOrder and assign it to new Cart.
It did not work as result was a nasty output. On saving Purchase Order for new cart, OrderForm from Parent was assigned to new cart and parent Purchase order lost its associated OrderForms.

Option 2:
Clone the Purchase Order and create a cart based on that but it will not work because. (I tested this with EpiServer Commerce R2SP2)
Type ‘Mediachase.Commerce.Orders.PurchaseOrder‘ in Assembly ‘Mediachase.Commerce, Version=5.2.628.0, Culture=neutral, PublicKeyToken=6e58b501b34abce3’ is not marked as serializable.
Solution:
We can clone OrderForms and OrderAddresses. Therefore I created a clone copy of OrderForm and order addresses and added that into the Cart.
Below piece of code helped me to achieve this. Note I have not tested/run workflows.
Cart mycart = OrderContext.Current.GetCart(“replacementcart”, SecurityContext.Current.CurrentUserId);
                CopyCart(mycart, Order);
//Delete and added a new Payment(Zero Charged)
//Convert into Purchase Order

        /// <summary>
        /// Copy cart
        /// </summary>
        /// <param name=”_cart”></param>
        /// <param name=”orderGroup”></param>
        public static void CopyCart(Cart _cart, PurchaseOrder orderGroup)
        {
            // Initial validation
            if (_cart == null) throw new ArgumentNullException(“_cart”);
            if (orderGroup == null) throw new ArgumentNullException(“orderGroup”);
            // need to set meta data context before cloning
            MetaDataContext.DefaultCurrent = OrderContext.MetaDataContext;
            OrderForm of = orderGroup.OrderForms[0].Clone() as OrderForm;
           
            // Remove existing Order Forms
            for (int i = _cart.OrderForms.Count-1 ; i>=0;i–)
            {
                _cart.OrderForms[i].Delete();
            }
            //Add order Forms to basket from PurchasOrder.
            _cart.OrderForms.Add(of);
            // Remove existing Order Addresses
            for (int i = _cart.OrderAddresses.Count-1 ; i>=0;i–)
            {
                _cart.OrderAddresses[i].Delete();
            }
            foreach (OrderAddress address in orderGroup.OrderAddresses)
            {
                MetaDataContext.DefaultCurrent = OrderContext.MetaDataContext;
                OrderAddress oa = address.Clone() as OrderAddress;
                _cart.OrderAddresses.Add(oa);
            }           
            _cart.SetParent(_cart);
            _cart.AddressId = orderGroup.AddressId;
            _cart.AffiliateId = orderGroup.AffiliateId;
            _cart.ApplicationId = orderGroup.ApplicationId;
            _cart.BillingCurrency = orderGroup.BillingCurrency;
            _cart.CustomerId = orderGroup.CustomerId;
            _cart.CustomerName = orderGroup.CustomerName;
            _cart.ProviderId = orderGroup.ProviderId;
            _cart.Status = orderGroup.Status;
            _cart.Owner = orderGroup.Owner;
            _cart.OwnerOrg = orderGroup.OwnerOrg;
            _cart.ShippingTotal = orderGroup.ShippingTotal;
            _cart.SiteId = orderGroup.SiteId;
            _cart.SubTotal = orderGroup.SubTotal;
            _cart.TaxTotal = orderGroup.TaxTotal;
            _cart.Total = orderGroup.Total;
          
            _cart.AcceptChanges();
        }

Get the CatalogNode of Product Listing Page

 

There are some instances when we need to know the Parent nodes of a Page based on [catalogue] Product Listing. E.g. I have a scenario when we are setting up header menus for products. Editor can select a parent Node for display child nodes as menus. But episerver classes doesnot provide any public function to return the CatalogNode of an active Product listing page. Following piece of code can help us in these scenarios.
private stringnodeName = string.Empty;
//Is it a category listing Page
bool isCatalogNodePage = BreadcrumbsFactory.IsCatalogNodePage(CurrentPage);
//Is it a product listing page
bool isProductPage = BreadcrumbsFactory.IsProductPage(CurrentPage);
           
//Get the parent catalog node/s of this page.
if (isCatalogNodePage)
{
CatalogNode node = CurrentPage.GetParentCatalogNodeCode();
       if (node != null)
              nodeName = node.ID;
       }
       else if(isProductPage)
       {
              CatalogNode node = CurrentPage.GetParentCatalogNodeCode();
              do
              {
              if(node != null)
              {
                     if (string.IsNullOrEmpty(nodeName))
                     nodeName = node.ID;
                     else
nodeName = string.Format(“{0}|{1}”, nodeName, node.ID);
node = CatalogContext.Current.GetCatalogNode(node.ParentNodeId);
              }
              } while(node.ParentNodeId>0);
}
/// <summary>
        /// Get CatalogNode of this page
        /// </summary>
        /// <param name=”page”></param>
        /// <returns></returns>
        privatestatic CatalogNode GetParentCatalogNodeCode(this PageData page)
        {
            if(BreadcrumbsFactory.IsProductPage(page))
            {
                stringtext = page[“ec”] as string;
                if(!string.IsNullOrEmpty(text))
                {
                    EntrycatalogEntry = CatalogContext.Current.GetCatalogEntry(text);
                    CatalogRelationDtocatalogRelationDto = CatalogContext.Current.GetCatalogRelationDto(0, 0, catalogEntry.CatalogEntryId, string.Empty, new CatalogRelationResponseGroup(CatalogRelationResponseGroup.ResponseGroup.NodeEntry));
                    if(catalogRelationDto.NodeEntryRelation.Count > 0)
                    {
                        int catalogNodeId = catalogRelationDto.NodeEntryRelation[0].CatalogNodeId;
                        returnCatalogContext.Current.GetCatalogNode(catalogNodeId);
                    }
                }
            }
            else
            {
                if(BreadcrumbsFactory.IsCatalogNodePage(page))
                {
                    stringtext2 = page[“CatalogNode”] as string;
                    if(!string.IsNullOrEmpty(text2))
                    {
                        CatalogNode catalogNode = CatalogContext.Current.GetCatalogNode(text2);
                        return CatalogContext.Current.GetCatalogNode(catalogNode.ParentNodeId);
                    }
                }
            }
            returnnull;
        }