Building a custom SOLR index in Sitecore

Building a custom SOLR index in Sitecore

  1. The Task
  2. Planning and groundwork
  3. Preparing SOLR
  4. Computed Field
  5. Configuration
  6. Finishing up
  7. Troubleshooting

The Task

Todays task is to create a dedicated Sitecore Index on a pre-existing site. Here are some of the specifics of the site that pertain to the task:

  • Sitecore 9.3 instance
  • SOLR cloud V8.1.1
  • Currently Master/Web/Core all indexing all fields
  • Indexing a cluster of items in a specific path, with specific field needs

The Index will need to meet the following requirements:

TaskAcceptance Criteria
only store locations indexed (path: sitecore/home/our-locations/)Able to query the index and only see these items
only fields needed should be indexedNot indexing all fields, showing only what is required
Both a Master and Web versionShould be one of each, easily identified
Location data is indexed in coordinatescoordinate type indexed

At a minimum, the tasks in the above should be completed for the task to be considered “completed” – there will always be adjustments needed for the business after the initial work.

One thing to note, this work can also be done use the ObjectFactory in the config, but that is not what we want to do today. Perhaps we will explore that another time.


Planning and groundwork

To begin, we need to layout roughly what we need, this will essentially create a checklist so when you complete, you know not only the requested tasks are completed, all items should be accounted for as well. There is always room to add in items after the fact, and this can help you in the future, unless it is something exclusive to this project.

Here is how I lay out my mental map for configs I will need to build out.

.
└── sitecoreRoot/
    └── App_Config/
        └── Include/
            └── CustomIndex/
                ├── Client.Locations.Master.config
                ├── Client.Locations.Web.config
                ├── Client.Locations.Defaults.config
                ├── Client.Locations.SearchIndex.Master.config
                └── Client.Locations.SearchIndex.Web.config

These would be placeholder files that I will go into detail in later, but here is a short overview of what they are and their purpose, you are free to names or merge these as you see fit.

  • Custom index folder
    • Helps separate the overrides specifically for this index. Personal preference mostly.
  • Master and Web separate configs
    • For readability and to have single responsibility per config override (especially in established solutions with many overrides)
  • client.locations.searchindex.master/web.config
    • This is the config insert with the key for calling when looking at search indexes. I will add this name/key to a constants file in my solution.
  • client.locations.defaults.config
    • contains a custom fitted set of defaults for our new solr index

One other task to keep in mind is adding this index reference to a constants file if your solution has one. Keeps you from having to call it by key name every time.

As with most of my guides, I simply hand you the recipe, you may follow it how you like.


Preparing SOLR

Quickly on the SOLR side of things, you’ll need to initialize a fresh empty index based on your organization schema, most of the time this can be done in the SOLR UI, but it’s not uncommon to have to manually add the folder and files.

Creating a new solr core via the solr UI core admin,

To manually add these, you can create a new index folder with the proper name, and then copy over the schema and config from another index if you have existing ones like so:

navigate to ‘\solr\solr-8.1.1\server\solr\’ and keep this window in the background. I prefer to do my adjustment outside of this folder first, and then copy to this directory. This avoids any strangeness in syncing. (You could also stop the service to perform this task too.)

In another window, navigate to a staging folder outside of the solr directories. Copy over the Core folder or create a new folder using the same naming convention as most of your indexes “client_locations_index_master” for example.

Within in, make the following directories:

.
└── client_location_index_master/
    ├── core.properies
    ├── conf/
    │   ├── lang/
    │   ├── managed-schema
    │   ├── params.json
    │   ├── prowords.txt
    │   ├── solrconfig.xml
    │   ├── stopwords.txt
    │   └── synonyms.txt
    └── data/
        ├── index
        ├── snapshot_metadata
        └── tlog

In the core.properties file, add the following information:

name=client_locations_index_master
update.autoCreateFields=false

For the other files, I advise copying from an existing core folder.

If you utilize ZooKeeper to manage your indexes and schemas, then you will need to push that change to them, otherwise the other nodes should see this new index.


Computed Field

I won’t go super deep into this, as I will assume this will be something not too unknown a concept. There is plenty of info online about this.

I advise you will want to take a look at the documentation for the spatial field config from the Sitecore docs, as that is the important part.

The 2 ‘fake’ computed fields we will be adding are coordinate field, and a name field.

The name field is assumed to have this logic for simplicity sake:

				var locationName = indexable.DisplayName ?? indexable.Name;

For the coordinate field, Sitecore provides a prebuilt for this, but because my current example is a pre-existing site and not one built for this purpose, we will be taking their current latitude and longitude fields they were using, and repurpose them into a computed coordinate type for indexing

...        
protected override object ComputeFieldValue(Item indexable)
        {
            try { 
			double latitude, longitutde;

			// Attempt to parse the lat and lng on the item as a double
			double.TryParse(indexable.Fields[LocationItems.Constants.Coordinates_LatitudeFieldId].GetValue(true), out latitude);
			double.TryParse(indexable.Fields[LocationItems.constants.Coordinates_LongitudeFieldId].GetValue(true), out longitutde);

			return new Coordinate(lat: latitude, lon: longitutde);
			}
			catch(Exception e)
            {
				// Very obvious fake coordinate when wrong.
                _logging.LogError($"Failed to compute coordinate for the item | {some-index-key-reference-here}", e);
				return new Coordinate(21.42, -21.42);
            }
		}

		protected override bool ShouldCompute(SitecoreIndexableItem indexable)
		{
			// add more templates as needed, could also add a check that the itemID != templateId
			return indexable.Item.TemplateID.Equals(Search.Constants.LocationItemTemplateId);
		}

Again a quick overview of what is going on:

  • try/catch in case something goes wrong, at the very least log it
  • attempt to parse the field values from the index item as doubles – can never be too careful and minimal impact since this is at the time of index it will be triggered.
  • return the coordinate object with the new values indexed
  • a shouldcompute to make sure we are hitting only items with our template, this is good practice so not every item gets this field added, especially with a client with a large index such as this one.

Configuration

Here we will get into the details more, this will be setting up those configs to meet our needs.

To start this, let’s build out the more complex one, which is the ‘Client.Locations.Defaults.config’ file.


Side note on how I found out how to do this

This is actually where I had trouble finding this information online on how to do this particular thing, as most of the docs point to doing it with the assumption you want to use the same exact settings as your master/web indexes. Which is NOT what we wanted to do. This client wanted something custom for this purpose, as their master and web indexes were quite large.

So, in order to do this, I took an index config from Sitecore and Coveo used by the solution and did a brief comparison and saw how things were hooked up via tags. It will make more sense in the next part.


I based this off a replica of the defaultIndex.config provided by Sitecore under ContentSearch, and heavily modified it to fit my need.

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:search="http://www.sitecore.net/xmlconfig/search/">
  <sitecore role:require="Standalone or ContentManagement or ContentDelivery" search:require="solr">
    <contentSearch>
      <!-- Configuration sections for indexes -->
      <indexConfigurations>
        <clientLocationsIndexConfiguration type="Sitecore.ContentSearch.SolrProvider.SolrIndexConfiguration, Sitecore.ContentSearch.SolrProvider">


			<!-- DEFAULT FIELD MAPPING 
               This field map allows you to take full control over how your data is stored in the index. This can affect the way data is queried, performance of searching and how data is retrieved and casted to a proper type in the API. 
            -->
          <fieldMap type="Sitecore.ContentSearch.SolrProvider.SolrFieldMap, Sitecore.ContentSearch.SolrProvider">
            <!-- This element must be first -->
            <typeMatches hint="raw:AddTypeMatch">
              <typeMatch typeName="guidCollection"     type="System.Collections.Generic.List`1[System.Guid]"     fieldNameFormat="{0}_sm"  multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="textCollection"     type="System.Collections.Generic.List`1[System.String]"   fieldNameFormat="{0}_txm" multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="stringCollection"   type="System.Collections.Generic.List`1[System.String]"   fieldNameFormat="{0}_sm"  multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="intCollection"      type="System.Collections.Generic.List`1[System.Int32]"    fieldNameFormat="{0}_im"  multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="guid"               type="System.Guid"                                        fieldNameFormat="{0}_s"                                         settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="id"                 type="Sitecore.Data.ID, Sitecore.Kernel"                  fieldNameFormat="{0}_s"                                         settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="shortid"            type="Sitecore.Data.ShortID, Sitecore.Kernel"             fieldNameFormat="{0}_s"                                         settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="string"             type="System.String"                                      fieldNameFormat="{0}_s"                                         settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="text"               type="System.String"                                      fieldNameFormat="{0}_t"   cultureFormat="_{1}"                  settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="int"                type="System.Int32"                                       fieldNameFormat="{0}_tl"                                        settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="bool"               type="System.Boolean"                                     fieldNameFormat="{0}_b"                                         settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="datetime"           type="System.DateTime"                                    fieldNameFormat="{0}_tdt" format="yyyy-MM-dd'T'HH:mm:ss.FFF'Z'" settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="long"               type="System.Int64"                                       fieldNameFormat="{0}_tl"                                        settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="float"              type="System.Single"                                      fieldNameFormat="{0}_tf"                                        settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="double"             type="System.Double"                                      fieldNameFormat="{0}_td"                                        settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="stringArray"        type="System.String[]"                                    fieldNameFormat="{0}_sm"  multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="intArray"           type="System.Int32[]"                                     fieldNameFormat="{0}_im"  multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="datetimeArray"      type="System.DateTime[]"                                  fieldNameFormat="{0}_dtm" multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="datetimeCollection" type="System.Collections.Generic.List`1[System.DateTime]" fieldNameFormat="{0}_dtm" multiValued="true"                    settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
              <typeMatch typeName="coordinate"         type="Sitecore.ContentSearch.Data.Coordinate, Sitecore.ContentSearch.Data" fieldNameFormat="{0}_rpt"                      settingType="Sitecore.ContentSearch.SolrProvider.SolrSearchFieldConfiguration, Sitecore.ContentSearch.SolrProvider" />
            </typeMatches>

            <!-- This allows you to map a field name in Sitecore to the index and store it in the appropriate way -->
            <!-- Add schema fields here to enable multi-language processing -->
            <fieldNames hint="raw:AddFieldByFieldName">
              <field fieldName="title"                returnType="text" />
              <field fieldName="type"                 returnType="text" />
            </fieldNames>

            <!-- FIELD TYPE MAPPING
                 This allows you to map a field type in Sitecore to a type in the index.
                 USAGE: When you add new field types to Sitecore, add the mappings here so they work through the Linq Layer 
              -->
            <fieldTypes hint="raw:AddFieldByFieldTypeName">
              <fieldType fieldTypeName="checkbox"                                                                                                 returnType="bool"             />
              <fieldType fieldTypeName="date|datetime"                                                                                            returnType="datetime"         />
              <fieldType fieldTypeName="html|rich text|single-line text|multi-line text|text|memo|image|reference"                                returnType="text"             />
              <fieldType fieldTypeName="word document"                                                                                            returnType="text"             />
              <fieldType fieldTypeName="integer"                                                                                                  returnType="long"             />
              <fieldType fieldTypeName="number"                                                                                                   returnType="float"            />
              <fieldType fieldTypeName="icon|droplist|grouped droplist"                                                                           returnType="string"           />
              <fieldType fieldTypeName="checklist|multilist|treelist|tree list|treelistex|tree list|multilist with search|treelist with search"   returnType="stringCollection" />
              <fieldType fieldTypeName="name lookup value list|name value list"                                                                   returnType="stringCollection" />
              <fieldType fieldTypeName="droplink|droptree|grouped droplink|tree"                                                                  returnType="stringCollection" />
            </fieldTypes>
          </fieldMap>

          <documentOptions type="Sitecore.ContentSearch.SolrProvider.SolrDocumentBuilderOptions, Sitecore.ContentSearch.SolrProvider">
            <!-- This flag will index all fields by default. This allows new fields in your templates to automatically be included into the index.
               You have two choices : 
               
               1) Set this to true and place all the fields you would like to remove in the 'ExcludeField' list below.
               2) Set to false and place all fields you would like to be indexed in the 'IncludeField' list below.
            -->
            <indexAllFields>false</indexAllFields>

            <!-- GLOBALLY INCLUDE TEMPLATES IN INDEX
                 This setting allows you to only include items that are based on specific templates in the index. Template inheritance will be
                 checked if checkTemplateInheritance is enabled.
                 When you enable this setting, all the items that are based on other templates are excluded, regardless of whether the template
                 is specified in the ExcludeTemplate list or not.
            -->
            <include hint="list:AddIncludedTemplate">
					<LocationsLandingPages>{TEMPLATE-GUID-HERE}</LocationsLandingPages>
            </include>

            <!-- This flag will enable checking template inheritance / base templates when specifying templates to include
                 or exclude from the index. This eliminates the need to explicitly specify all templates to include or
                 exclude, but may affect performance.
            -->
            <checkTemplateInheritance>false</checkTemplateInheritance>

			 <!-- GLOBALLY INCLUDE FIELDS IN INDEX
               This setting allows you to specify which fields to include in the index when the indexAllFields setting is set to false.
            -->
            <include hint="list:AddIncludedField">
				<MySitecoreFieldName>{FIELD-GUID-HERE}</MySitecoreFieldName>
            </include>


            <fields hint="raw:AddComputedIndexField">
				  <field fieldName="customfield"     returnType="string"     >ClientSite.Common.Search.ComputedFields.LocationNameComputedField,ClientSite.Common.Search</field>
				  <field fieldName="coordinates_pin" returnType="coordinate" >ClientSite.Common.Search.ComputedFields.CoordinatePinComputedField,ClientSite.Common.Search</field>
			</fields>
		</documentOptions>

          <!-- SITECORE FIELDTYPE MAP
               This maps a field type by name to a Strongly Typed Implementation of the field type e.g. html maps to HTMLField
            -->
          <fieldReaders type="Sitecore.ContentSearch.FieldReaders.FieldReaderMap, Sitecore.ContentSearch">
            <param desc="id">defaultFieldReaderMap</param>
            <mapFieldByTypeName hint="raw:AddFieldReaderByFieldTypeName">
              <fieldReader fieldTypeName="checkbox"                                             fieldReaderType="Sitecore.ContentSearch.FieldReaders.CheckboxFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="date|datetime"                                        fieldReaderType="Sitecore.ContentSearch.FieldReaders.DateFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="image"                                                fieldReaderType="Sitecore.ContentSearch.FieldReaders.ImageFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="single-line text|multi-line text|text|memo"           fieldReaderType="Sitecore.ContentSearch.FieldReaders.DefaultFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="integer"                                              fieldReaderType="Sitecore.ContentSearch.FieldReaders.NumericFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="number"                                               fieldReaderType="Sitecore.ContentSearch.FieldReaders.PrecisionNumericFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="html|rich text"                                       fieldReaderType="Sitecore.ContentSearch.FieldReaders.RichTextFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="multilist with search|treelist with search"           fieldReaderType="Sitecore.ContentSearch.FieldReaders.DelimitedListFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="checklist|multilist|treelist|treelistex|tree list"    fieldReaderType="Sitecore.ContentSearch.FieldReaders.MultiListFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="icon|droplist|grouped droplist"                       fieldReaderType="Sitecore.ContentSearch.FieldReaders.DefaultFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="name lookup value list|name value list"               fieldReaderType="Sitecore.ContentSearch.FieldReaders.NameValueListFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="droplink|droptree|grouped droplink|tree|reference"    fieldReaderType="Sitecore.ContentSearch.FieldReaders.LookupFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="attachment|frame|rules|tracking|thumbnail"            fieldReaderType="Sitecore.ContentSearch.FieldReaders.NullFieldReader, Sitecore.ContentSearch" />
              <fieldReader fieldTypeName="file|security|server file|template field source|link" fieldReaderType="Sitecore.ContentSearch.FieldReaders.NullFieldReader, Sitecore.ContentSearch" />
            </mapFieldByTypeName>
          </fieldReaders>

          <!-- INDEX FIELD STORAGE MAPPER 
               Maintains a collection of all the possible Convertors for the provider.
            -->
          <indexFieldStorageValueFormatter type="Sitecore.ContentSearch.SolrProvider.Converters.SolrIndexFieldStorageValueFormatter, Sitecore.ContentSearch.SolrProvider">
            <converters hint="raw:AddConverter">
              <converter handlesType="System.Guid"                                                          typeConverter="Sitecore.ContentSearch.Converters.IndexFieldGuidValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.Data.ID, Sitecore.Kernel"                                    typeConverter="Sitecore.ContentSearch.Converters.IndexFieldIDValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.Data.ShortID, Sitecore.Kernel"                               typeConverter="Sitecore.ContentSearch.Converters.IndexFieldShortIDValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="System.DateTime"                                                      typeConverter="Sitecore.ContentSearch.Converters.IndexFieldUTCDateTimeValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="System.DateTimeOffset"                                                typeConverter="Sitecore.ContentSearch.Converters.IndexFieldDateTimeOffsetValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="System.TimeSpan"                                                      typeConverter="Sitecore.ContentSearch.Converters.IndexFieldTimeSpanValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.ContentSearch.SitecoreItemId, Sitecore.ContentSearch"        typeConverter="Sitecore.ContentSearch.Converters.IndexFieldSitecoreItemIDValueConvertor, Sitecore.ContentSearch">
                <param type="Sitecore.ContentSearch.Converters.IndexFieldIDValueConverter, Sitecore.ContentSearch"/>
              </converter>
              <converter handlesType="Sitecore.ContentSearch.SitecoreItemUniqueId, Sitecore.ContentSearch"  typeConverter="Sitecore.ContentSearch.SolrProvider.Converters.SolrIndexFieldSitecoreItemUniqueIDValueConverter, Sitecore.ContentSearch.SolrProvider">
                <param type="Sitecore.ContentSearch.Converters.IndexFieldItemUriValueConverter, Sitecore.ContentSearch"/>
              </converter>
              <converter handlesType="Sitecore.Data.ItemUri, Sitecore.Kernel"                               typeConverter="Sitecore.ContentSearch.Converters.IndexFieldItemUriValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.Globalization.Language, Sitecore.Kernel"                     typeConverter="Sitecore.ContentSearch.Converters.IndexFieldLanguageValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="System.Globalization.CultureInfo"                                     typeConverter="Sitecore.ContentSearch.Converters.IndexFieldCultureInfoValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.Data.Version, Sitecore.Kernel"                               typeConverter="Sitecore.ContentSearch.Converters.IndexFieldVersionValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.Data.Database, Sitecore.Kernel"                              typeConverter="Sitecore.ContentSearch.Converters.IndexFieldDatabaseValueConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.ContentSearch.IIndexableId, Sitecore.ContentSearch"          typeConverter="Sitecore.ContentSearch.Converters.IndexableIdConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.ContentSearch.IIndexableUniqueId, Sitecore.ContentSearch"    typeConverter="Sitecore.ContentSearch.Converters.IndexableUniqueIdConverter, Sitecore.ContentSearch" />
              <converter handlesType="Sitecore.ContentSearch.Data.Coordinate, Sitecore.ContentSearch.Data"  typeConverter="Sitecore.ContentSearch.Converters.IndexFieldCoordinateValueConverter, Sitecore.ContentSearch" />
            </converters>
          </indexFieldStorageValueFormatter>
          <documentBuilderType>Sitecore.ContentSearch.SolrProvider.SolrDocumentBuilder, Sitecore.ContentSearch.SolrProvider</documentBuilderType>
          <defaultSearchSecurityOption ref="contentSearch/indexConfigurations/defaultSearchSecurityOption" />
          <enableReadAccessIndexing ref="contentSearch/indexConfigurations/enableReadAccessIndexing" />
        </clientLocationsIndexConfiguration>
      </indexConfigurations>
    </contentSearch>
  </sitecore>
</configuration>

I have highlighted the lines that were modified most from the initial copy, and removed a lot of the thing I was not using.

To quickly highlight the changes:

  • Added custom tag wrapper ‘clientLocationsIndexConfiguration’
  • Added the coordinate solr field mapping to account for our new computed field
  • Set index all fields to false
  • Added my template for the landing pages of locations, as that is all I want to index
  • set inheritance check to false, as I do not want to index templates
  • added any non-computed field guids I want, the tag name does not matter
  • added the references to my new computed fields for this index
  • The rest of the file is considered ‘stock’ aside from the removal of config nodes not used

The reason we leave the types and mappers, is because we still want to utilize the converters and field mappers that come out of the box for any Sitecore fields we want to index, I do not believe there is a negative impact from doing this. You also could use the ‘ref’ tag here to call the stock config nodes, but this is for a cleaner setup, to avoid and reliance on the stock config.

Now we need to move on to the ‘Client.Locations.Master.config’ and ‘Client.Locations.Web.config’ modifications. We split these for ease of reading.

I will provide one example, and I think it should be enough to do both on your own, otherwise, godspeed.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:search="http://www.sitecore.net/xmlconfig/search/">
	<sitecore role:require="Standalone or ContentDelivery or ContentManagement" search:require="solr">
		<contentSearch>
			<configuration type="Sitecore.ContentSearch.ContentSearchConfiguration, Sitecore.ContentSearch">
				<indexes hint="list:AddIndex">
					<index id="client_locations_index_master" type="Sitecore.ContentSearch.SolrProvider.SolrSearchIndex, Sitecore.ContentSearch.SolrProvider">
						<param desc="name">$(id)</param>
						<param desc="core">client_locations_index_master</param>
						<param desc="propertyStore" ref="contentSearch/indexConfigurations/databasePropertyStore" param1="$(id)" />
						<configuration ref="contentSearch/indexConfigurations/clientLocationsIndexConfiguration" />
						<strategies hint="list:AddStrategy">
							<strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/manual" role:require="(ContentManagement and !Indexing) or (ContentDelivery and !Indexing)" />
							<strategy ref="contentSearch/indexConfigurations/indexUpdateStrategies/onPublishEndAsyncSingleInstance" role:require="Standalone or (ContentManagement and Indexing) or (ContentDelivery and Indexing)" />
						</strategies>
						<locations hint="list:AddCrawler">
							<crawler type="Sitecore.ContentSearch.SitecoreItemCrawler, Sitecore.ContentSearch">
								<Database>master</Database>
								<Root>/sitecore/content/locations/</Root>
							</crawler>
						</locations>
						<enableItemLanguageFallback>false</enableItemLanguageFallback>
						<enableFieldLanguageFallback>false</enableFieldLanguageFallback>
					</index>
				</indexes>
			</configuration>
		</contentSearch>
	</sitecore>
</configuration>

So to quickly explain what is going on here:

  • This config will add an index with key ‘client_locations_index_master’ to the config properties
  • adds in that this should write to the property store (the table in SQL that tells Sitecore CM’s which is the primary/secondary index)
  • Tells it what index configuration to look at when doing index operations (note the name on line 11 matches the node on the defaults.config)
  • Roles are defined as well for which index strategy to use – in this case manual indexing for non-processing server – but you’ll want to reference one that works for your solution
  • Adds in a crawler that tells it where to look when index operations are performed – I have this narrowed to a specific path

This should be a 1:1 but with web if you plan to use a web version – you could use the same index for both if you wanted to.

The last config you’ll want to modify is our ‘Client.Locations.SearchIndex.Master.config’ and web configs to make sure the key is there, and useable in our constants file. This should inject the index in a searchindex area of the config. Again, you will want to do the exact same, but with web.

<?xml version="1.0" encoding="utf-8" ?>
<configuration
	xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"
	xmlns:patch="http://www.sitecore.net/xmlconfig/"
	xmlns:set="http://www.sitecore.net/xmlconfig/set/">
	<sitecore>
		<settings>
			<setting xdt:Transform="Insert"
				name="Client.SearchIndexes.LocationsMasterIndex"
				value="client_locations_index_master" />
		</settings>
	</sitecore>
</configuration>

If you have a constants file, you can simply add a line like so:

		public static string LocationsIndexName = Sitecore.Configuration.Settings.GetSetting("Client.SearchIndexes.LocationsMasterIndex");

BONUS POINTS:

For some bonus points, you can implement a switch on rebuild configuration for these indexes. This would require creating aliases in SOLR for the rebuild and active index, and then also creating cores for the rebuild and active index.

The config itself would be straight forward, using the .example provided from Sitecore, it would look a little like this, placed in perhaps a config override folder, or simply named with a Z.Index_Name.SwitchOnRebuild.config style name.

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:search="http://www.sitecore.net/xmlconfig/search/">
  <sitecore role:require="Standalone or ContentManagement" search:require="Solr">
    <contentSearch>
      <configuration type="Sitecore.ContentSearch.ContentSearchConfiguration, Sitecore.ContentSearch">
        <indexes hint="list:AddIndex">
		   <index id="client_locations_index_master">
            <patch:attribute name="type" value="Sitecore.ContentSearch.SolrProvider.SwitchOnRebuildSolrCloudSearchIndex, Sitecore.ContentSearch.SolrProvider" />
            <param desc="core">
              <patch:delete />
            </param>
            <param desc="rebuildcore">
              <patch:delete />
            </param>
            <param patch:after="*[@desc='name']" desc="mainalias">client_locations_index_masterMainAlias</param>
            <param patch:after="*[@desc='mainalias']" desc="rebuildalias">client_locations_index_masterRebuildAlias</param>
            <param patch:after="*[@desc='rebuildalias']" desc="collection">client_locations_index_master</param>
            <param patch:after="*[@desc='collection']" desc="rebuildcollection">client_locations_index_master_rebuild</param>
          </index>
        </indexes>
      </configuration>
    </contentSearch>
  </sitecore>
</configuration>

Finishing up

Now that you have all of that out of the way, it’s almost time to test the index.

There are a couple of checks you must perform before proceeding however, to make sure nothing got lost.

  1. Verify you see the index in the config when looking at the config viewer ({CLIENTurl}/sitecore/admin/ShowConfig.aspx)
  2. In the Sitecore control panel, you see your index listed in the index manager
  3. Do a quick audit of the logs to make sure there is nothing off related to your newly created index

If all of these check out, then we can proceed with attempting to rebuild this new index.

if not, I will add possible troubleshooting steps at the bottom of this article.

Should it rebuild successfully, you should now be able to go to your SOLR panel and see the new core, whether in the core admin or just via the dropdown. If you can see it, I recommend querying it and double checking your objects look good there.

Seeing all of these succeed, you should now be able to use it in your queries and whatever it is needed. In this case, I will be plotting these points on a map for end users.

var context = ContentSearchManager.GetIndex(Constants.LocationsIndexName).CreateSearchContext();

Troubleshooting

During setup, I ran into a few issues, so I figured a troubleshooting section would be helpful. These may be more specific to the setup I was working with, but hopefully you can adapt these to your situation as needed.

My Index is not visible in Sitecore?

If your index is not seen in the index manager within Sitecore, there are a few quick and easy things to do. The following assumes you’ve made the indexes in SOLR already and can see your default indexes.

  1. This is the second things, because the first will determine if this needs to be done. Assuming you do see your configs correctly, and the index exists within SOLR, then simply restarting both the Sitecore instances and the Solr instances is a good idea. I have only had to do this once, and it was because initialize on startup was off.
  2. The first, is to check that your configurations are seen in your showconfig page within Sitecore. This is found through the administration page or through this path: sitecore/admin/ShowConfig.aspx – From here you can simply Ctrl+F and search your index name and validate it looks correct and that there were no incorrect roles provided via the config, or perhaps even a misspelling.
  3. Attempt to populate the managed schema within the Sitecore admin page, this should be under the index manager options. This has helped before, but not promises

My switch on rebuild for SOLR cloud is not working?

If your switch on rebuild is not working correctly – i.e. unavailable results during rebuilding – Then it is most likely one of the following:

  1. The first thing to check if you are using switch on rebuild, is that the config is seen, again checking the showconfig page in the administration page, as that will tell you if it is indeed patched by your config and with what data that is patched.
  2. Verify you have the correct index strategy set up. You can use any of the out of box strategies, but just do your research on which fits best in your solution. Some of these go well together, some of them do not. Sitecore has a good doc going over these options and what works well together.
  3. I would also check your aliases are set up correctly as well. These can be seen on your SOLR instance via the API query: {YOUR_INSTANCE}:8983/solr/admin/collections?action=LISTALIASES


Leave a comment

Damien Rincon

From Debug to Deploy: Lessons from the Fullstack Trenches.