Sunday 28 September 2014

Sorting Sitecore SubItems programmatically

6 comments
In one of my previous blog post I’ve explained that Sitecore Fast Query doesn’t return hierarchical result set as compared to Sitecore content tree structure and use Sitecore Query instead of Fast Query if your code logic depends on Sitecore content tree structure and you have to render the items in the same order as they are appearing in Sitecore Content tree. Due to performance constraint I’ve to use Sitecore Fast Query as it is more efficient in terms of execution time and performance as compared to Sitecore Query. Now the main catch is that I’ve to retrieve items using Fast Query and display items with respect to the Sitecore content tree hierarchy.
 
Depending on the total number of items and where these items are located in Sitcore Content tree; it can be much faster and efficient to sort items using C# code after getting result using Sitcore Fast Query. In one scenario, I was getting 12 items in 1758ms using Sitecore Query and 70ms using Sitecore Query. Thus sorting results using C# code will be much efficient in this scenario.

Subitems Sorting in Sitecore Content Tree

Firstly we have to figure it out how Sitecore actually sorts these items in content tree. I’ve created few test items under article folder in Sitecore content tree.
By default Sitecore sorts item by their name.  We can also sort these items manually by using the buttons in the Sorting group of the Home strip or right click on the item and choose Sorting.
If you are changing sort order of a field manually then Sitecore will insert a value in the Sortorder (__sortorder) field in the Appearance section of the standard fields. Sortorder field is used to determine the ordering between sibling items.
Sortorder value takes preference over the default sorting by name. The Item's children are sorted first by Sortorder and then by Name.

Sitecore has few inbuilt subitems sorting methods.  To use one of the built in subitems sorting method, right click on the parent item and choose Sorting and Subitems Sorting.
Please note that the Sortorder field is the first criteria for most sorting. To reset the Sortorder field of the subitems, click Reset in Subitems Sorting dialog. This will set the value of all the sortorder fields to 100.
Sitecore has the following six in built in subitems sorting methods:
  1. Created : Sorts the items in ascending order by their created time.
  2. Default : Sorts the items in ascending order by their names where numbers are treated by their ASCII code value instead of their numerical value. Default sorting is case insensitive. For example:
  3. Display Name : Sorts the items in ascending order by their display names. If an item does not have a display name then the item name is used for sorting. This sorting is a logical sorting, where numbers are considered by their numerical value instead of ASCII code value.  For example:
  4. Logical : Sorts the items in ascending order by their names where numbers are treated by their numerical value instead of their ASCII code value. For example:
  5. Reverse : Sorts the items in descending order by their names where numbers are treated by their ASCII code value instead of their numerical value. For example:
  6. Updated : Sorts the items by their updated time. The item that was updated last is sorted first in order.
If you do not specify a child sorting rule for an item then Sitecore applies the default child sorting rule, and users can sort items manually.

Sorting Sitecore SubItems programmatically

Whenever you select one of the built in subitems sorting method by right clicking on the parent item and selecting Sorting and Subitems Sorting then Subitems Sorting (__Subitems Sorting) field in the Appearance section of the standard fields of parent item gets updated with selected sorting method.
As shown in above image; I’ve selected Reverse Subitems sorting method and Subitems Sorting field gets updated with value as ‘Reverse’. I’ve written following C# code to sort items after getting result using Sitcore Fast Query.
// Get Parent Item i.e Article Folder 
Item parentItem = Sitecore.Context.Database.GetItem(new ID("{D6272FF7-9315-4DCC-B50A-BF8F216E5388}"));
// Get all children under Article Folder using Sitecore Query
string query = "fast://*[@@id ='{D6272FF7-9315-4DCC-B50A-BF8F216E5388}']/*";
Item[] items = Sitecore.Context.Database.SelectItems(query);
// Sort the items in the same way they are appearing in Content Tree by calling SitecoreSubItemsSort Method. Pass items array and Parent Item as parameter
items = SitecoreSubItemsSort(items, parentItem);
// Or call overloaded SitecoreSubItemsSort Method by just passing items array as parameter
items = SitecoreSubItemsSort(items);
        /// <summary>
        /// Sort a list of Items the same way that Sitecore does when it displays them in the content tree
        /// </summary>
        /// <param name="itemArray">Item Array to be sorted</param>
        /// <param name="parentItem">Parent Item</param>
        /// <returns>Sorted Item Array</returns>
        public static Item[] SitecoreSubItemsSort(Item[] itemArray, Item parentItem)
        {
            // Sort items by Subitems sorting method
            if (itemArray.Length > 0)
            {
                string sorting = parentItem.Fields["__Subitems Sorting"].Value.ToString();
                IComparer<Item> comparer = ComparerFactory.GetComparer(sorting, parentItem.Database);
                Array.Sort<Item>(itemArray, 0, itemArray.Length, comparer);
            }
            return itemArray;
        }

        /// <summary>
        /// Sort a list of Items the same way that Sitecore does when it displays them in the content tree
        /// </summary>
        /// <param name="itemArray">Item Array to be sorted</param>
        /// <param name="parentItem">Parent Item</param>
        /// <returns>Sorted Item Array</returns>
        public static Item[] SitecoreSubItemsSort(Item[] itemArray)
        {
            // Sort items by Subitems sorting method
            if (itemArray.Length > 0)
            {
                IComparer<Item> comparer = ComparerFactory.GetComparer(itemArray.First().Parent);
                Array.Sort<Item>(itemArray, 0, itemArray.Length, comparer);
            }
            return itemArray;
        }

        /// <summary>
        /// Sort a list of Items the default way that Sitecore does when it displays them in the content tree
        /// </summary>
        /// <param name="itemList">List to be sorted</param>
        /// <returns>Sorted list</returns>
        public static Item[] SitecoreDefaultSort(List<Item> itemList)
        {
            // Sort by sortorder, if sortorder value is empty, assume 100. 
            // Sort items with the same sortorder value by Name. 
            // Sitecore sorts in the same way. 

            itemList.Sort(
                (a, b) =>
                (
                    (a.Fields["__sortorder"].Value == "" ? "100" : a.Fields["__sortorder"].Value)
                     ==
                    (b.Fields["__sortorder"].Value == "" ? "100" : b.Fields["__sortorder"].Value)
                )
                ?
                (string.Compare(a.Name, b.Name))
                :
                (int.Parse(a.Fields["__sortorder"].Value == "" ? "100" : a.Fields["__sortorder"].Value).CompareTo(int.Parse(b.Fields["__sortorder"].Value == "" ? "100" : b.Fields["__sortorder"].Value)))
                );
            return itemList.ToArray();
        } 
Create IComparer object based on Subitems Sorting field by calling GetComparer method of Sitecore.Data.Comparers.ComparerFactory class.  Then sort the items by using IComparer. If the value of Subitems Sorting field is null or empty then items will be sorted by using default sorting. Using Array.Sort method is much efficient. LINQ's OrderBy operator is about five times slower than Array.Sort with large sequences. Comments and suggestions are most welcome. Happy coding!
References:
  1. Create Your Own Subitems Sorting Comparer by Mike Reynolds
  2. Sitecore SDN Post

6 comments :

  1. Could you share the project source code?

    ReplyDelete
  2. Is there an easy way to do this in SQL?

    ReplyDelete
  3. If I want to find pages that include a sublayout/control with the Item ID {86C15AC5-661B-4288-A997-DCB251CC739E} I can run the following.

    use xxx_Core

    select top 10 *
    from Links
    where SourceDatabase = 'master'
    and TargetItemID = '86C15AC5-661B-4288-A997-DCB251CC739E'

    I haven't implemented it yet, but it's not as full-featured as the C# code in the original article would be.

    ReplyDelete
  4. Check out Web API 2. It's even easier to do. Less work

    ReplyDelete