Parsing the __Tracker object for indexing

I was recently working on a project where I had to index Sitecore profile data for aggregation and speed of access. I remember watching this YouTube video put on by Martin Davies (Twitter). In the video he gave some good examples of how to use Sitecore CXM profiling to filter and facet data, but he also mentioned how slow the TrackingField object is in Sitecore. I think it does a lot more processing than any of us need when we are using the TrackingField for indexing.

The code you usually see for the TrackingField is below. All that does is convert the field "__Tracking" into an object, and like I mentioned earlier, it must dig down deep and pull back a lot more data than we need.


TrackingField field = new TrackingField(i.Fields["__Tracking"]);
                ContentProfile[] profiles = field.Profiles;

Martin mentioned parsing through the xml in the tracking field is faster, but didn't reference any code that I could use in my project. So I wrote some quick code that would parse the field for me.

First we should know what the xml looks like for the tracking code. The following is the tracking code for a single item in Sitecore. The item has two profiles set and each profile is defined with two profile keys.

<tracking>
  <profile id="{350BF2EE-ABA2-4E8A-9A9F-9800746A6570}" name="Buyer Persona" presets="finance|100||">
    <key name="Finance" value="5" />
    <key name="Maintenance" value="0" />
  </profile>
  <profile id="{E0F73165-0B83-41AB-8EE8-41B8D7081FD8}" name="Market" presets="golf course|100||">
    <key name="Golf Course" value="5" />
    <key name="Municipality" value="0" />
  </profile>
</tracking>

First I built this little utility class that accepts an XML string. We take that XML, parse out the profiles and then parse out the keys associated to those profiles. I build them up into a POCO class, XdbProfile. This class holds the keys in another POCO class XdbProfileKeys. A List of these XdbProfiles is what I return to the requester. With this object, you can do as we please with the data.



namespace Contoso.Utilities
{
    public class TrackingConverter
    {
        public static List ProcessCookie(string xml)
        {

            var doc = XElement.Parse(xml);
            IEnumerable childList =
                from el in doc.Elements()
                select el;

            var profiles = new List();

            foreach (XElement c in childList)
            {
                var xdbProfile = new XdbProfile { Name = c.Attribute("name").Value, Keys = new List() };

                IEnumerable prof = from profile in c.Elements()
                                             select profile;

                foreach (XElement p in prof)
                {
                    var profileKey = new XdbProfileKey { Name = p.Attribute("name").Value };

                    int k = 0;
                    int.TryParse(p.Attribute("value").Value, out k);
                    profileKey.Value = k;

                    xdbProfile.Keys.Add(profileKey);
                }
                profiles.Add(xdbProfile);
            }

            return profiles;
        }
    }

    public class XdbProfile
    {
        public string Name { get; set; }
        public List Keys { get; set; }

    }

    public class XdbProfileKey
    {
        public string Name { get; set; }
        public int Value { get; set; }
    }
}

For me, I am using it for a computed index field. In the remmed out code, you can see the old way of doing it with the TrackingField.



class TagFacet : IComputedIndexField
    {
        public string FieldName { get; set; }
        public string ReturnType { get; set; }
        public object ComputeFieldValue(IIndexable indexable)
        {
            var i = ((Item)(indexable as SitecoreIndexableItem));
            if (i != null && i["__Tracking"] != String.Empty)
            {
                //TrackingField field = new TrackingField(i.Fields["__Tracking"]);
                //ContentProfile[] profiles = field.Profiles;

                //List profileList = new List();
                //foreach (ContentProfile profile in profiles)
                //{
                //    foreach (var key in profile.Keys)
                //    {
                //        if (key.Value > 0)
                //        {
                //            if (!String.IsNullOrWhiteSpace(key.Key))
                //            {
                //                profileList.Add(key.Key + "|" + key.Value);
                //            }
                //        }
                //    }
                //}

                var trackingField = TrackingConverter.ProcessCookie(i.Fields["__Tracking"].Value);

                var profileList = new List();
                foreach (var profile in trackingField)
                {
                    foreach (var key in profile.Keys)
                    {
                        if (key.Value > 0)
                        {
                            if (!String.IsNullOrWhiteSpace(key.Name))
                            {
                                profileList.Add(key.Name + "|" + key.Value);
                            }
                        }
                    }
                }

                if (profileList.Count == 0)
                    return null;
                else
                    return profileList;
            }
            return null;
        }
    }

Now for Data. I love data.

When running my index of master using the TrackingField, the index took 68 seconds.

Tracker

When I used the XML parsing, the same index took 46 seconds.

xml

What is important here is that I only have 4 items that have profile data. It's 12 seconds difference to call up the TrackingField on just 4 items.

I hope this helps you.