Indexing Multilist/Treelist fields and reading them back

Whilst in the process of ensuring Multilist/Treelist fields are being indexed and pulled back correctly, I came across a very useful blog post to assist in converting a list of GUID’s in the index back to an ID array

http://mikerobbins.co.uk/2013/12/17/sitecore-7-search-id-array-type-converter/

This works like a dream 🙂 Don’t forget to update your index config to reflect that you want to ensure that your field is being indexed (below is an example). The important part of this is ensuring “type” is set to “System.GUID”

<field fieldName="updated_specifications" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.GUID" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider">
<analyzer type="Sitecore.ContentSearch.LuceneProvider.Analyzers.LowerCaseKeywordAnalyzer, Sitecore.ContentSearch.LuceneProvider" />
</field>

Also, you need to alter the definition of Droptree (or TreeListEx, or whatever field you are indexing) in <fieldTypes> section. By default the storageType is set to “NO”, change this to “YES”

Reindex your site with the TypeConverter and alterations to your index config and you should be good to go 🙂

Order of Sitecore Items utilising Sort Order and Created Date

A few times the ordering of objects when retrieving from Sitecore have caught me out. In a nutshell, the Sort Order field on an Item helps determines how Items are ordered, or an ordering “strategy” can be applied on the Item’s Parent to determine how the Items are sorted.

This is great, however it appears to be a little hit & miss as to whether the field will get populated if it is not understood what is happening. By default, if there is no sorting strategy specified by the Parent (by the Subitem Sorting field) then inserting an Item will leave the Sort Order field blank unless you rearrange the ordering of the items or change the Parent’s Subitem Sorting field (at which point you will see the Sort Order field populate by Sitecore). However, what if you copy, clone or move an item from another position in the tree? What if the Content Editor actually removes the Sort Order value from the field? You will end up in a position where some items have the Sort Order set and others do not (the field will be blank).

Also, if you are pulling items out of an Index (Lucene or Solr), the order of the results may not be the same as the order that they are in Sitecore. Thankfully this can be handled easily with a bit of code 🙂

As the Sort Order in Sitecore is a string field, you cannot simply perform an OrderBy() on the field as the ordering will be based on string ordering, not numeric ordering. Your results will be different.

This can cause some issues when querying items and you want to maintain the order that they appear in the CMS. The below code shows how I deal with that. Basically, I have a function delegate which handles the conversion form a string Sort Order to an integer value – if Sort Order is empty then it’s value will be 0.

private static Func<Item, int> GetSortOrderValue(List<Item> i)
{
 return x => string.IsNullOrEmpty(i[0][Sitecore.FieldIDs.Sortorder]) ? 0 : int.Parse(i[0][Sitecore.FieldIDs.Sortorder]);
}

Now we can utilise the above in a Linq statement so that we can order by Sort Order, and then order by Create Date (to cater for Items that have the same Sort Order)

private List<Item> OrderItems(List<Item> i)
{
    i = i.OrderBy(GetSortOrderValue(i)).ThenBy(y => y.Statistics.Created).ToList();
    return i;
}

Hope this makes sense 🙂

Sitecore Searching returns all languages from the Index

A quick one for today, something to be mindful of when performing a Sitecore Search against the indexes – what will be returned from your search will be a SearchResultItem for all languages that you have stored in your index, not just the language context.

You will need to perform the language filtering yourself 🙂
Quickest way of doing this is to either filter after the search has been done:


if (searchHit.Document.Language == Sitecore.Context.Language.Name)
{

  // your other code

}

.. or make it part of the search query


SearchResults<SearchResultItem> searchResults = context.GetQueryable<SearchResultItem>()
  .Filter(x => x.TemplateId == filterTemplace)
  .Filter(y => y.Language == Sitecore.Context.Language.Name)
  .GetResults();

Simple eh?

Item Buckets and maintaining Parent Relationships

Item Buckets are a fantastic idea, they allow the Content Editor to store a very large number of items into a folder (called a Bucket) and allows the Content Editor to search for items contained within that bucket – all from the CMS. However, there are a number of things you need to bear in mind when using Item Buckets. Say for example you have a Blog Post with Comments underneath the Post as child items. You could use the following Sitecore Search to pull out all of the Comments that are relating to the Post.

This can be achieved by performing the following search query:

public List<Item> GetComments(Item commentsFolder)
 {
 ISearchIndex searchIndex = ContentSearchManager.GetIndex(Utilities.IndexUtilities.GetIndex());
 List<Item> returnItems = new List<Item>();
 using (IProviderSearchContext context = searchIndex.CreateSearchContext())
 {
 SearchResults<SearchResultItem> searchResults = context.GetQueryable<SearchResultItem>().Filter(x => x.Parent == commentsFolder.ID).GetResults();

 foreach (var searchHit in searchResults.Hits)
 {
// Do something with the comment
 }
 }

 return returnItems;
 }

This example takes the Post’s “Comments Folder” as a parameter and returns all the child items of that Comments Folder.

Now, you wish to turn the Comments folder into an Item Bucket. This is a great idea as there could be thousands of comments per post. What you will instantly notice though is that once you make the Comments folder an Item Bucket, Sitecore will reorganise the Items within a Bucket and place them in folders in accordance with the Bucketing Strategy – this can be evidenced by observing that the path to each Content Item in the Bucket changes. This also means that you cannot use the above code to perform the search as the parent of a Comment will no longer be the Comments Folder.

This is not necessarily an issue, as on the template you specify as an Item Bucket, you can specify the “Lock Child Relationship” feature in the item template:

 

Lock Child Relationship

This will ensure that any children within a Bucket will not inherit the path imposed on them by the Bucketing Strategy (therefore the above code will work for that scenario).

However, what if you want a combination of both? What if you want to maintain a Bucketing Strategy AND maintain parent-child relationshop? Well, one strategy we can use is adding a Computed Field into the index and into to your entity called CommentFolderId.

You can create a class that knows hows to work out the parent folder of a comment and store this in the index. With this class, it utilises the extension method GetParentBucketItemOrParent() from the Sitecore.Buckets.dll so ensure you have a reference to this.


namespace SitecoreJim.Business.ComputedFields
{
 public class CommentsFolderIdComputedField : IComputedIndexField
 {
 public object ComputeFieldValue(IIndexable indexable)
 {
 Assert.ArgumentNotNull(indexable, "indexable");
 SC.ContentSearch.SitecoreIndexableItem scIndexable =
 indexable as SC.ContentSearch.SitecoreIndexableItem;

 SC.Data.Items.Item item = (SC.Data.Items.Item)scIndexable;

 if (item == null)
 {
 Log.Log.Warn(
 this + " : unsupported SitecoreIndexableItem type : " + scIndexable.GetType());
 return false;
 }

 // Check to see whether the Item is the item we wish to store the parent folder in the index
 if ( (item.TemplateID != new ID(Constants.WritingCommentTemplateString))
 && (item.TemplateID != new ID(Constants.PollCommentTemplateString)))
 return false;

 string stringToIndex = string.Empty;

 Item parentItem = item.GetParentBucketItemOrParent();
 stringToIndex = parentItem.ID.ToString().Replace("-", "").Replace("{", "").Replace("}", "");

 return stringToIndex;
 }

 public string FieldName { get; set; }
 public string ReturnType { get; set; }
 }
}

95% of that code was copied off someone’s blog post (apologies – I can’t remember who it is to credit them!!). The important bit of this is a call to:

GetParentBucketItemOrParent()

This will ascertain what the “real” parent item is of an item that is in a bucket.

Now update Sitecore.ContentSearch.Lucene.DefaultIndexConfiguration.config file so that the index can make use of this. Firstly, add the following field definition into the <fields> section:


<field fieldname="commentsfolderid" storateType="yes" indexType="tokenized">SitecoreJim.Business.ComputedFields.CommentsFolderIdComputedField,SitecoreJim.Business</field>

Then, in the section <fieldNames hint=”raw:AddFieldByFieldName”> you need to add the following so that Sitecore knows what the field type is:

<field fieldName="commentsfolderid" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.GUID" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider">
<analyzer type="Sitecore.ContentSearch.LuceneProvider.Analyzers.LowerCaseKeywordAnalyzer, Sitecore.ContentSearch.LuceneProvider" />
</field>

Once you rebuild the indexes, the new computed field will become part of the index.

If you haven’t already, you need to create an entity that inherits from SearchResultItem, add the property into the class for CommentsFolderId and decorate the property with the informational identify which field it relates to in the index:


public class WritingComment : SearchResultItem
{
[IndexField("commentsfolderid")]
public Guid CommentsFolderId { get; set; }

public Item InnerItem { get; set; }
}

All that needs to occur now is to slightly modify the search query to make use of this computed field. So what we are now saying get me all the comments that relate to a given Comments folder (which is unique to a post).


using (IProviderSearchContext context = searchIndex.CreateSearchContext())
{
SearchResults<WritingComment> searchResults = context.GetQueryable<WritingComment>()
.Filter(x => x.CommentsFolderId == commentsFolder.ID).GetResults();

foreach (var searchHit in searchResults.Hits)
{
// Do something with the comment</pre>
}
}

There we go, now you can turn the Comments Folder into an Item Bucket whilst still maintaining the parent-child relationship.

Hope this helps 🙂