Back to Basics: Fetch Solr Index Field Name for Sitecore Fields Using Solr FieldNameTranslator

I have been meaning to write this post for a while as I have been using this nifty little feature in all my content search API implementations using the Solr provider.

Unsafe: Defining SearchResultItem classes using hard coded Solr fields names for IndexField data annotation attributes as follows:

[IndexField("page_title")]
public string PageTitle { get; set; }


 Problems:
  • Cause runtime errors when field names change in Sitecore
  • Hard coded strings, sort of magic values
  • Unmanageable code
Safe: Define SearchResultItem classes using strongly typed constants for data annotation attributes as follows:

[IndexField(SampleItem.PageTitleSolrIndexFieldName)]
public string PageTitle { get; set; }

Solution:
  • Compile time errors are way better than runtime errors
  • More manageable code
  • More readable code
  • More dynamic query building
Trick: Fetch Solr translated field name values on template fields using FieldNameTranslator and reference them  with code generation.

Step 1: Create a base template to store the Solr Index Field Name under /sitecore/templates/System/Templates/Sections.



Step 2: Inherit the base template onto Template Field template.at /sitecore/templates/System/Templates/Template field



Step 3: Write an ItemSaved event handler that will generate the value for Solr Index Field Name.

using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Data.Managers;
using Sitecore.Diagnostics;
using Sitecore.Events;
using Sitecore.SecurityModel;
using System;
using System.Collections.Generic;

namespace SomeNameSpace
{
    public class ItemSavedEvent
    {
        public string Database { get; set; }

        public void OnItemSaved(object sender, EventArgs args)
        {
            var t = new Dictionary();
            //get the arguments as Sitecore args, and make sure it isn't null
            var eventArgs = args as SitecoreEventArgs;
            Assert.IsNotNull(eventArgs, "eventArgs");

            //grab the current item from the event args, and make sure it isn't null
            var item = eventArgs.Parameters[0] as Item;
            Assert.IsNotNull(item, "item");

            //do not process if the item is not derived from Solr base template inherited on Template Field item
            if (!IsDerived(item, new ID("{2201D7D1-C260-45D1-90AA-5FC1F822417A}")))
            {
                return;
            }

            try
            {
                item = UpdateSolrIndexFieldName(item);
                Log.Info(string.Format("ITEM_SAVE_UPDATE: {0}", item.Fields["Solr Index Field Name"]), this);
            }
            catch (Exception ex)
            {
                Log.Error(string.Format("ITEM_SAVE_EXCEPTION: {0}", ex.Message), this);
            }
        }

        private Item UpdateSolrIndexFieldName(Item item)
        {
            if (item != null)
            {
                try
                {
                    using (new SecurityDisabler())
                    {
                        item.Editing.BeginEdit();
                        try
                        {
                            item["Solr Index Field Name"] = SearchHelper.GetSolrFieldName(item.Name, item.ID);
                        }
                        catch (Exception ex)
                        {
                            Log.Error(string.Format("ITEM_SAVE_EXCEPTION: {0}", ex.Message), this);
                            throw;
                        }
                        finally
                        {
                            item.Editing.EndEdit();
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.Error(string.Format("ITEM_SAVE_EXCEPTION: {0}", ex.Message), this);
                    throw;
                }
            }

            return item;
        }

        private bool IsDerived(Item item, ID templateId, bool checkSecurity = false)
        {
            if (item == null)
                return false;

            if (templateId.IsNull)
                return false;

            var state = SecurityState.Enabled;
            if (!checkSecurity) state = SecurityState.Disabled;

            using (new SecurityStateSwitcher(state))
            {
                var templateItem = item.Database.Templates[templateId];

                var isDerived = false;
                if (templateItem != null)
                {
                    var template = TemplateManager.GetTemplate(item);
                    if (template == null) return false;
                    isDerived = template.ID == templateItem.ID || template.DescendsFrom(templateItem.ID);
                }

                return isDerived;
            }
        }
    }
}

Here is the cool part that actually fetches the Solr index field name value using FieldNameTranslator from the Solr provider:

using Glass.Mapper.Sc;
using Sitecore.ContentSearch;
using Sitecore.ContentSearch.Linq;
using Sitecore.ContentSearch.Linq.Utilities;
using Sitecore.ContentSearch.SolrProvider;
using Sitecore.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using Sitecore.Data.Items;

namespace SomeNameSpace
{
    public static class SearchHelper
    {
        private static ISearchIndex _masterIndex = SolrContentSearchManager.GetIndex("sitecore_master_index");
        
        public static string GetSolrFieldName(string sitecoreFieldName, ID sitecoreFieldId)
        {
            //for system fields replace double _ with single _ and spaces with empty string
            if (sitecoreFieldName.StartsWith("__")) return sitecoreFieldName.Replace("__", "_").Replace(" ", string.Empty).ToLower();
            var masterDb = Sitecore.Data.Database.GetDatabase("master");

            //get Sitecore field type
            var sitecoreFieldType = masterDb.GetItem(sitecoreFieldId).Fields["Type"].Value;

            //get Sitecore field type name
            var fieldTypeConfiguration = _masterIndex.Configuration.FieldMap.GetFieldConfigurationByFieldTypeName(sitecoreFieldType);
            try
            {
                //convert to system field type
                var type = Type.GetType(fieldTypeConfiguration.Attributes["type"]);

                //get the Solr field name based on the Sitecore field name and Sitecore field type.
                //if fieldNameTranslator is null, you have not configured Solr correctly
                //http://www.sitecorevarun.com/2015/09/resolved-sitecore-solr-provider-error.html
                return _masterIndex.FieldNameTranslator.GetIndexFieldName(sitecoreFieldName, type);
            }
            catch (Exception ex)
            {
                //for all custom sitecore types that are not defined in the field map in default index configuration
                throw ex;
            }
        }
    }
}


Register the event with a patch config.

<configuration>
  <sitecore>
    <events>
      <!-- Custom -->
      <event name="item:saved">
        <handler method="OnItemSaved" type="SomeNameSpace.ItemSavedEvent, SomeBinary">
        </handler>
      </event>
      <!-- END Custom -->
    </events>
  </sitecore>
</configuration>

Step 4: Update your T4 template to generate the code with field names as constants similar to:

 /// SampleItem
 /// 
 /// Path: /sitecore/templates/Sample/Sample Item 
 /// ID: 76036f5e-cbce-46d1-af0a-4143f9b557aa 
 /// 
 [SitecoreType(TemplateId="76036f5e-cbce-46d1-af0a-4143f9b557aa")] //, Cachable = true
 public partial class SampleItem  : GlassBase, ISampleItem 
 {
   public const string TemplateIdString = "76036f5e-cbce-46d1-af0a-4143f9b557aa";
   public static readonly ID TemplateId = new ID(TemplateIdString);
   public const string TemplateName = "Sample Item";
   
     
   public static readonly ID TitleFieldId = new ID("75577384-3c97-45da-a847-81b00500e250");
   public const string TitleFieldName = "Page Title";
   public const string TitleSolrIndexFieldName = "Page_Title_t";
   
   ///    /// The Page Title field.
   /// Field Type: Single-Line Text  
   /// Field ID: 75577384-3c97-45da-a847-81b00500e250
   /// Custom Data: 
   /// 
   [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Team Development for Sitecore - GlassItem.tt", "1.0")]
   [SitecoreField(SampleItem.TitleFieldName)]
   public virtual string PageTitle  {get; set;}
     
   
 }


Although Step 4 seems rather straightforward, I am unable to fetch the Solr Index Field Name value using TDS provided T4 templates as follows as it returns an empty value:

public const string <#= GetPropertyName(field) #>SolrIndexFieldName = "<#=GetValue(field.SitecoreFields, "Solr Index Field Name")#>";

If you know a better way to fetch the value in T4 templates, please do share.

hope you find this post useful and as always please leave comments and suggestions for improvements.

Comments

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete

Post a Comment

Popular posts from this blog

RESOLVED: Sitecore Solr provider error: Value cannot be null. Parameter name: fieldNameTranslator.

Experience Marketing: The Crawl, Walk, Run of Sitecore - Part 1 - The Crawl Phase