Skip to main content

Microsoft

Getting to Know PredefinedQuery with Sitecore Content Search

Recently I’ve been working a lot with the Sitecore Content Search API and found myself writing a lot of code like this:

var allTheThings = context.GetQueryable<Thing>().Where(x => x.TemplateId == Constants.Thing.TemplateId && x.IsSearchable);

As we’re always looking for better ways to do things around here and my laziness started to prevail, I thought, “there has got to be a way to do this where I don’t have to keep checking for template congruence in my search queries….” The obvious subsequent thought was “I should write something to do this for me…”
Then one fateful day, I fired up the Sitecore LINQ Scratchpad (/sitecore/admin/linqscratchpad.aspx for the uninitiated) for something unrelated and noticed a line in the boilerplate code that I hadn’t paid attention to previously:

Wait, what is this “PredefinedQuery” attribute?
Through some digging and reflection as well as some trial and error, it turns out that Sitecore already wrote something to do what I wanted for me (thanks Sitecore!).
So here’s what it does. When context.GetQueryable<T>()  gets called, the method uses reflection against the specified generic type (in my example above, that would be Thing) and checks to see if there are [PredefinedQuery]  attributes on the class. The [PredefinedQuery]  attribute translates the parameters into a lambda expression (likely using a PredicateBuilder) that GetQueryable<T>  applies to the IQueryable that it returns.
So my example above can be rewritten using the [PredefinedQuery]  attribute like so:

[PredefinedQuery("TemplateId", ComparisonType.Equal, Constants.Thing.TemplateId, typeof(Guid))]
public class Thing
{
    [IndexField("_template")]
    public Guid TemplateId {get; set; }
    [IndexField("isSearchable_b")]
    public bool IsSearchable {get; set;}
}
var allTheThings = context.GetQueryable<Thing>().Where(x => x.IsSearchable);

When context.GetQueryable<Thing>()  gets called, it basically returns the equivalent of an instance of IQueryable<Thing> .Where(x => x.TemplateId == Constants.Thing.TemplateId)  so it’s one less check I need to do every time I use my Thing class for search results.
Before you go rush off to play with this, there are a few things to be aware of…
The first parameter is the string value of the name of the property on the class and not the search index field name – this means in this case it’s “TemplateId” and not “_template”. This also means that any field you want to use in a [PredefinedQuery]  attribute needs to be a property on the class and mapped to the appropriate field in the search index with the [IndexField]  attribute.
The second parameter defines the comparison type according to an enum. The available values are below:

  • Equal
  • LessThan
  • LessThanOrEqual
  • GreaterThan
  • GreaterThanOrEqual
  • OrderBy
  • Like
  • NotEqual
  • Contains
  • StartsWith
  • EndsWith
  • Matches
  • MatchWildcard

There is also an optional 4th parameter that you can use to specify the type of the value (the 3rd parameter). In my example I had a property in a Constants class that is a string but I can let the query know that the string should be parsed into a Guid.
Eagle-eyed readers may have noticed that I used plurals in talking about the [PredefinedQuery]  attribute earlier. That’s because, you guessed it, you can have multiple instances of it on a single class. They stack with what’s essentially an AND operator as it creates a .Where()  statement for each instance of the [PredefinedQuery]  attribute and chains them together. With that in mind, let’s revisit the example once more.

[PredefinedQuery("TemplateId", ComparisonType.Equal, Constants.Thing.TemplateId, typeof(Guid))]
[PredefinedQuery("IsSearchable", ComparisonType.Equal, true, typeof(bool))]
public class Thing
{
    [IndexField("_template")]
    public Guid TemplateId {get; set; }
    [IndexField("isSearchable_b")]
    public bool IsSearchable {get; set;}
}
var allTheThings = context.GetQueryable<Thing>();

Look ma, no .Where()  statement! Now every time I use GetQueryable  on the Thing class, I’ll always get objects returned that match the correct TemplateId  and also have the IsSearchable  flag marked as true. Those are conditions I no longer have to check every time I need to execute a search as they’ll already be built in to the IQueryable  that gets returned to me. Neat, right?
The best part is that because these lambda operations are happening on an IQueryable , the code still gets compiled down to a single query when the search is executed even if you add additional LINQ operators after GetQueryable<T>() . Now, go search all the things! (with responsible type-checking)
Have you used the [PredefinedQuery]  attribute? What other cool ways have you used it?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

George Chang

More from this Author

Categories
Follow Us
TwitterLinkedinFacebookYoutubeInstagram