Lee Crowe
Jul 4, 2011
  6035
(1 votes)

EPiCommerce and Multiline Dictionary Meta Field Bug

I am in the early stages of a large EPiCommerce build and myself and one of my colleagues have been experiencing issues with Dictionary meta fields that allow multiple selections to be picked.

The Problem

Basically the issue is that the values chosen in these fields do not appear in the faceted navigation.  Some time last week we raised a support ticket with EPiServer to investigate the issue.

Today, on the way home from work I thought I would investigate this issue further and see if it is possible to find a work around to the problem.  It just so happens there is Smile

After using LUKE – Lucene Index Toolbox to analyse the indexes that were being created I could see that the values being stored for my multiline enabled dictionary were always System.String[].

When the CatalogEntry was being indexed the indexer was not sure what to do with the particular field type and just performed a ToString() on the string array of selected values and stored that in the index, that’s s**t Sad smile

The Fix

I last used Mediachase around 3-4 years ago and it seems the documentation and online forum support doesn’t seem much better than it was back then.

So I did some digging around through the various Mediachase configs and found this little beauty Mediachase.Search.config:

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <Mediachase.Search>
   3:    <SearchProviders defaultProvider="LuceneSearchProvider">
   4:      <providers>
   5:        <!--<add name="SolrSearchProvider" type="Mediachase.Search.Providers.Solr.SolrSearchProvider, Mediachase.Search.SolrSearchProvider" queryBuilderType="Mediachase.Search.Providers.Solr.SolrSearchQueryBuilder, Mediachase.Search.SolrSearchProvider" url="http://localhost:8080/solr" shareCores="true" />-->
   6:        <add name="LuceneSearchProvider" type="Mediachase.Search.Providers.Lucene.LuceneSearchProvider, Mediachase.Search.LuceneSearchProvider" queryBuilderType="Mediachase.Search.Providers.Lucene.LuceneSearchQueryBuilder, Mediachase.Search.LuceneSearchProvider" storage="C:\EPiServer\Sites\EPiServerCommerceManager3\App_Data\SearchIndex" />
   7:      </providers>
   8:    </SearchProviders>
   9:    <Indexers basePath="C:\EPiServer\Sites\EPiServerCommerceManager3\App_Data\SearchIndex">
  10:      <add name="catalog" type="Mediachase.Search.Extensions.Indexers.CatalogIndexBuilder, Mediachase.Search.Extensions" />
  11:    </Indexers>
  12:  </Mediachase.Search>

 

This config allows you to configure a search provider, you will see mine is configured to use the LuceneSearchProvider.  You will also notice that you are able to configure an indexer Surprised smile.

I then decompiled the CatalogIndexBuilder and had a scan around the various methods and decided to implement my own Indexer which inherits the Medicachase.Search.Extensions.Indexers.CatalogIndexBuilder.

There is an AddMetaField method you can override and this contains all of the logic for populating the meta fields within the lucene document.

I then figured out where the problem code was.  Basically there was an if statement like the following:

   1:  if (metaField.DataType == MetaDataType.DictionaryMultiValue)

Which needed to change to:

   1:  if (metaField.DataType == MetaDataType.DictionaryMultiValue || metaField.DataType == MetaDataType.EnumMultiValue)

When one of your meta fields is a dictionary which allows multiple selections it’s MetaDataType is an EnumMutiValue.  This MetaDataType was not handled correctly within the out of the box Medichase CatalogIndexBuilder.

I then created a new class named LeesCatalogIndexBuilder within a new project named LeesSearchExtensions.  I got hold of the decompiled code for the AddMetaField method and fixed the issue above and placed it within my new Indexbuilder class.

The code for the class is below:

 

   1:  namespace LeesSearchExtensions
   2:  {
   3:      using System;
   4:      using System.Collections;
   5:      using System.Collections.Generic;
   6:      using Mediachase.Commerce.Storage;
   7:      using Mediachase.MetaDataPlus;
   8:      using Mediachase.MetaDataPlus.Configurator;
   9:      using Mediachase.Search.Extensions;
  10:      using Mediachase.Search.Extensions.Indexers;
  11:   
  12:      public class LeesCatalogIndexBuilder : CatalogIndexBuilder
  13:      {
  14:   
  15:          protected override void AddMetaField(Mediachase.Search.Extensions.SearchDocument doc, Mediachase.MetaDataPlus.Configurator.MetaField metaField, System.Collections.Hashtable metaObject)
  16:          {
  17:              //base.AddMetaField(doc, metaField, metaObject);
  18:   
  19:              // Code taken from the base class virtual method
  20:              if (metaField.AllowSearch)
  21:              {
  22:                  string name = metaField.Name;
  23:                  List<string> list = new List<string>();
  24:                  if (metaField.Attributes["IndexStored"] != null)
  25:                  {
  26:                      if (metaField.Attributes["IndexStored"].Equals("true", StringComparison.OrdinalIgnoreCase))
  27:                      {
  28:                          list.Add(SearchField.Store.YES);
  29:                      }
  30:                      else
  31:                      {
  32:                          list.Add(SearchField.Store.NO);
  33:                      }
  34:                  }
  35:                  if (metaField.Attributes["IndexField"] != null)
  36:                  {
  37:                      if (metaField.Attributes["IndexField"].Equals("tokenized", StringComparison.OrdinalIgnoreCase))
  38:                      {
  39:                          list.Add(SearchField.Index.TOKENIZED);
  40:                      }
  41:                      else
  42:                      {
  43:                          list.Add(SearchField.Index.UN_TOKENIZED);
  44:                      }
  45:                  }
  46:                  object metaFieldValue = MetaHelper.GetMetaFieldValue(metaField, metaObject[metaField.Name]);
  47:                  string value = string.Empty;
  48:                  if (metaField.DataType == MetaDataType.BigInt || metaField.DataType == MetaDataType.Decimal ||
  49:                      metaField.DataType == MetaDataType.Float || metaField.DataType == MetaDataType.Int ||
  50:                      metaField.DataType == MetaDataType.Money || metaField.DataType == MetaDataType.Numeric ||
  51:                      metaField.DataType == MetaDataType.SmallInt || metaField.DataType == MetaDataType.SmallMoney ||
  52:                      metaField.DataType == MetaDataType.TinyInt)
  53:                  {
  54:                      if (metaFieldValue != null)
  55:                      {
  56:                          value = metaFieldValue.ToString();
  57:   
  58:                          if (!string.IsNullOrEmpty(value))
  59:                          {
  60:                              doc.Add(new SearchField(name, metaFieldValue, list.ToArray()));
  61:                          }
  62:                      }
  63:                  }
  64:                  else
  65:                  {
  66:                      if (metaFieldValue != null)
  67:                      {
  68:                          if (metaField.DataType == MetaDataType.DictionaryMultiValue || metaField.DataType == MetaDataType.EnumMultiValue)
  69:                          {
  70:                              string[] array = (string[])metaFieldValue;
  71:   
  72:                              for (int i = 0; i < array.Length; i++)
  73:                              {
  74:                                  string text = array[i];
  75:                                  doc.Add(new SearchField(name, (text == null) ? string.Empty : text.ToLower(), list.ToArray()));
  76:                              }
  77:                          }
  78:                          else
  79:                          {
  80:                              if (metaField.DataType == MetaDataType.StringDictionary)
  81:                              {
  82:                                  MetaStringDictionary metaStringDictionary = metaFieldValue as MetaStringDictionary;
  83:                                  ArrayList arrayList = new ArrayList();
  84:                                  if (metaStringDictionary != null)
  85:                                  {
  86:                                      foreach (string key in metaStringDictionary.Keys)
  87:                                      {
  88:                                          string text = metaStringDictionary[key];
  89:                                          doc.Add(new SearchField(name, (text == null) ? string.Empty : text.ToLower(), list.ToArray()));
  90:                                      }
  91:                                  }
  92:                              }
  93:                              else
  94:                              {
  95:                                  doc.Add(new SearchField(name, (metaFieldValue == null) ? string.Empty : metaFieldValue.ToString().ToLower(), list.ToArray()));
  96:                              }
  97:                          }
  98:                      }
  99:                  }
 100:              }
 101:   
 102:          }
 103:      }
 104:  }

I then had to update the Mediachase.Search.config to use my new CatalogIndexBuilder:

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <Mediachase.Search>
   3:    <SearchProviders defaultProvider="LuceneSearchProvider">
   4:      <providers>
   5:        <add name="LuceneSearchProvider" type="Mediachase.Search.Providers.Lucene.LuceneSearchProvider, Mediachase.Search.LuceneSearchProvider" queryBuilderType="Mediachase.Search.Providers.Lucene.LuceneSearchQueryBuilder, Mediachase.Search.LuceneSearchProvider" storage="C:\EPiServer\Sites\EPiServerCommerceManager3\App_Data\SearchIndex" />
   6:      </providers>
   7:    </SearchProviders>
   8:    <Indexers basePath="C:\EPiServer\Sites\EPiServerCommerceManager3\App_Data\SearchIndex">
   9:      <!--<add name="catalog" type="Mediachase.Search.Extensions.Indexers.CatalogIndexBuilder, Mediachase.Search.Extensions" />-->
  10:      <add name="catalog" type="LeesSearchExtensions.LeesCatalogIndexBuilder, LeesSearchExtensions" />
  11:    </Indexers>
  12:  </Mediachase.Search>

 

Conclusion

I am sure you would agree with me that this kind of bug should have been picked up by the Medichase testing process, but we all know bugs like this slip through.  I don’t know whether other people have experienced this but multiple searches on google didn’t return any hits for the issue.

In summary, luckily it turned out there was a simple solution to the problem so myself and my colleagues can carry on with the out of the box faceted search functionality without having to reinvent the wheel Smile.

Thanks for reading Winking smile

Jul 04, 2011

Comments

Jul 5, 2011 11:46 AM

Great post!
It must be a bug.
Beside your fix, I think I found another workaround for it is that when creating the Dictionary metafield, you can enable "Editable" checkbox, then the type of our metafield will be EnumMultiValue and CatalogIndexBuilder can index it correctly

sandeep@r1group.co.uk
sandeep@r1group.co.uk Sep 20, 2011 10:03 PM

HI
Useful post. Would like to confirm that if you mark the dictionary as editable then the values are stored correctly in the lucene index. It also works fine in the faceted search.
Kind Regards
Sandeep

Please login to comment.
Latest blogs
Opti ID overview

Opti ID allows you to log in once and switch between Optimizely products using Okta, Entra ID, or a local account. You can also manage all your use...

K Khan | Jul 26, 2024

Getting Started with Optimizely SaaS using Next.js Starter App - Extend a component - Part 3

This is the final part of our Optimizely SaaS CMS proof-of-concept (POC) blog series. In this post, we'll dive into extending a component within th...

Raghavendra Murthy | Jul 23, 2024 | Syndicated blog

Optimizely Graph – Faceting with Geta Categories

Overview As Optimizely Graph (and Content Cloud SaaS) makes its global debut, it is known that there are going to be some bugs and quirks. One of t...

Eric Markson | Jul 22, 2024 | Syndicated blog

Integration Bynder (DAM) with Optimizely

Bynder is a comprehensive digital asset management (DAM) platform that enables businesses to efficiently manage, store, organize, and share their...

Sanjay Kumar | Jul 22, 2024

Frontend Hosting for SaaS CMS Solutions

Introduction Now that CMS SaaS Core has gone into general availability, it is a good time to start discussing where to host the head. SaaS Core is...

Minesh Shah (Netcel) | Jul 20, 2024

Optimizely London Dev Meetup 11th July 2024

On 11th July 2024 in London Niteco and Netcel along with Optimizely ran the London Developer meetup. There was an great agenda of talks that we put...

Scott Reed | Jul 19, 2024