Share

LinkedIn

Testing for the Existence of a Sitecore Field

This post goes back to the basics. Find out the best way to determine if you have the right kind of item.

A very common task in Sitecore development is to test an item to see if it is based on a particular template. Ancestor fallback, iterating over a folder of items and filtering search results are just a few cases where you might want to do this. For as long as I can remember, I have been using a method like the following for that purpose.

public static bool IsDerived(this Item item, ID templateId)
{
    if (item == null || templateId.IsNull)
    {
        return false;
    }
 
    if (item.TemplateID == templateId)
    {
        return true;
    }
 
    var template = TemplateManager.GetTemplate(item);
    return template != null && template.DescendsFromOrEquals(templateId);
}

Recently, I had a case where I had some items that I needed to ensure were based on a template that had only one field. Also, it was likely that the template was at least a few steps up the inheritance hierarchy. So it occurred to me that the above method might not be the most performant way to go. I thought instead, that it would be more efficient to just check for the existence the field that I was interested in, kind of like JavaScript feature detection.


 
I quietly congratulated myself on this long-overdue epiphany and threw down a bit of code something like this:

var field = item.Fields[FieldId];
if (field != null)
{
    // do stuff with field
}

And then several of my unit tests promptly erupted into angry red flames.

I found that the failing tests were the ones that were testing items that did not have the field in question. For some reason, asking for a field that did not exist on the item was still returning a field. I knew that something must be wrong. Then I started to question my own sanity. Asking for a field by name returns null, so why not by ID?


It turns out that when you use the ID-based indexer for the Fields collection, it simply news up a field with that ID. It’s not until you start using the properties of that Field object that it tries to get the field from the template.

I started looking for other ways to test for the existence of a field and was rather disappointed by what I found. Using the string-based indexer for the Fields collection to get the field by name works, but I wanted to follow best practices and use field IDs. Using item.Fields.Contains(fieldId) doesn’t work properly unless you first call item.Fields.ReadAll(). I found that testing item.Fields[fieldId].Definition != null works correctly, but it seems kind of hacky. Using TemplateManager.IsFieldPartOfTemplate(fieldId, item) also works correctly and, while I’m not crazy about using a static method, it’s better than the other options.


I decided to do some performance testing to see how these options compare. I created a 3-deep template hierarchy, with a field on the first level. I used Rocks to create 100 items based on the third-level template. Then I tested each of the methods that I had found that actually worked correctly in a loop over those 100 items. For comparison, I also tested the IsDerived extension method above. I cleared all the caches before the first run, but not the second run. Here are the results:



As you can see the IsDerived method was by far the slowest, coming in at over twice the time of the fastest method. Reading all of the fields and then using Fields.Contains was somewhat better. The remaining three methods were pretty comparable, but TemplateManager.IsFieldPartOfTemplate seemed to come out slightly ahead most of the time. It’s certainly not a big enough difference to make me want to go back and change old code. But, moving forward, I’ll try to use my new extension method:

public static bool HasField(this Item item, ID fieldId)
{
    return TemplateManager.IsFieldPartOfTemplate(fieldId, item);
}

Sitecore development, Sitecore custom code

Comments

Add a Comment

*
*

Please confirm you are human by typing the text you see in this image:

Chris Auer said: 10/29/2015 at 12:06 PM

Thanks for these tests. I ran into this problem a while ago and didn't have time to test. I just used the ReadAll() method. Errrr.