The Sitecore content API provides a very easy way of accessing your content. It is highly optimized for performance with internal caching. You can find a great overview of the different query strategies here.
But in some specific cases, the API will not provide you with the right tools to get what you want, with the performance that you want. In those cases you have 2 options:
- Persist a more suitable representation of your data. E.g.: output caching, Sitecore caching, Lucene indexing.
- Strip some overhead, so functionality that is not needed at that point can be skipped. An example will follow.
Both of these methods have their drawbacks, so it is important to evaluate them when choosing a solution. Some drawbacks for the first method:
- More storage/memory is needed.
- The data representation must be kept consistent with the original data (can result in bugs that are hard to spot if implemented incorrectly).
Drawbacks of the second method:
- Relies on the underlying implementation. You may have to change the implementation with future upgrades.
- If the stripped functionality turns out to be needed in the future, the implementation may become unsuitable.
An example of the second method
If we want to get the descendant nodes of a certain item, we can easily use the content API:
1 |
item.Axes.GetDescendants() |
But if we want to limit the descendants to items with a certain template, this is a better method:
1 |
item.Axes.SelectItems("descendant::*[@@TemplateName='File']")
|
However, in this case, we will not get items with template ‘Jpeg’ or ‘Pdf’. Those templates inherit from the ‘File’ template. So if we want to get all descendants that inherit from the ‘File’ template, we need a helper method:
1 2 3 4 5 6 7 |
private bool InheritsFromTemplate(TemplateItem templateItem, Sitecore.Data.ID templateId) { return templateItem.ID == templateId || (templateItem.BaseTemplates != null && templateItem.BaseTemplates .Where(baseTempl => InheritsFromTemplate(baseTempl, templateId)).Count() > 0); } |
You could make this into an extension method if you want. Here’s how we can use this method to get all the descendants that inherit from ‘File’:
1 2 |
item.Axes.GetDescendants() .Where(desc => InheritsFromTemplate(desc.Template, FileTemplateId)) |
This will be quite slow if there are many descendants. We will bypass the content API and query the SQL server database directly to make it much faster.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
/// <summary> /// A complex example that returns the actual descendants that inherit from a specific template id. /// </summary> /// <param name="item">The item for which to determine the descendants.</param> /// <param name="templateId">The ID of the template of which the result items have to inherit.</param> /// <returns>The actual descendants that inherit from the template that was passed.</returns> public IEnumerable<Item> GetDescendantsInheritingTemplate(Item item, Sitecore.Data.TemplateID templateId) { Assert.IsNotNull(item, "A valid Sitecore item must be provided to this method"); // query SQL server directly string query = @"with cteChildren as (select ID, TemplateID from [dbo].[Items] where ID = CAST(@itemId AS UNIQUEIDENTIFIER) union all select e.ID, e.TemplateID from cteChildren cte inner join [dbo].[Items] e on cte.ID = e.ParentID), cteSubItems as ( select ItemId from SharedFields where FieldId = '12C33F3F-86C5-43A5-AEB4-5598CEC45116' and Value like '%' + @templateId + '%' union all select sf.ItemId from SharedFields sf inner join cteSubItems ic on sf.Value like '%' + CAST(ic.ItemId as varchar(36)) + '%' where sf.FieldId = '12C33F3F-86C5-43A5-AEB4-5598CEC45116' ) select cteChild.ID from (select ItemId from cteSubItems union select @templateId) cteSit inner join cteChildren cteChild on cteSit.ItemId = cteChild.TemplateID"; Sitecore.Data.DataProviders.Sql.DataProviderReader reader = dataApi.CreateReader(query, "itemId", item.ID.ToString().Trim(new[] { '{', '}' }), "templateId", templateId.ID.ToString().Trim(new[] { '{', '}' })); // return to the content API to get the actual items (therefore also applies security) List<Item> result = new List<Item>(); while (reader.Read()) { Sitecore.Data.ID itemId = new Sitecore.Data.ID(reader.InnerReader.GetGuid(0)); if (itemId != item.ID) { Item descItem = Sitecore.Context.Database.GetItem(itemId); if (descItem != null) { result.Add(descItem); } } } return result; } |
Place the query in a stored procedure if you want to use this method in production. For reference, here is a different article which discusses bypassing the content API.
Measuring speed
In my opinion, any performance improvement (especially if it has significant drawbacks) must be proven to be worth it! So I’ve measured the speed of this method and a simpler one against the normal Sitecore way of doing things. Here’s the result:
As you can see, the performance gain can be significant. So if the drawbacks are acceptable, this may be a good option. Here is the aspx so you can test it for yourself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
<%@ Page Language="C#" AutoEventWireup="true" Debug="true" %> <%@ Import Namespace="Sitecore.Data.Items" %> <%@ Import Namespace="Sitecore.Diagnostics" %> <%@ Import Namespace="Sitecore.Data.SqlServer" %> <script runat="server"> private SqlServerDataApi dataApi = new SqlServerDataApi( Sitecore.Configuration.Settings.GetConnectionString(Sitecore.Context.Database.ConnectionStringName) ); /// <summary> /// A simple example that determines the number of descendants that an item has. /// Note that this method disregards any security settings! /// </summary> /// <param name="item">The item for which to count descendants.</param> /// <returns>The number of descendants.</returns> public int GetDescendantsCount(Item item) { Assert.IsNotNull(item, "A valid Sitecore item must be provided to this method"); string query = @"with cteChildren as (select ID from [dbo].[Items] where ID = CAST(@ItemId AS UNIQUEIDENTIFIER) union all select e.ID from cteChildren cte inner join [dbo].[Items] e on cte.ID = e.ParentID) select count(ID) - 1 from cteChildren"; Sitecore.Data.DataProviders.Sql.DataProviderReader reader = dataApi.CreateReader(query, "ItemId", item.ID.ToString().Trim(new[] { '{', '}' })); return reader.Read() ? reader.InnerReader.GetInt32(0) : 0; } /// <summary> /// A complex example that returns the actual descendants that inherit from a specific template id. /// </summary> /// <param name="item">The item for which to determine the descendants.</param> /// <param name="templateId">The ID of the template of which the result items have to inherit.</param> /// <returns>The actual descendants that inherit from the template that was passed.</returns> public IEnumerable<Item> GetDescendantsInheritingTemplate(Item item, Sitecore.Data.TemplateID templateId) { Assert.IsNotNull(item, "A valid Sitecore item must be provided to this method"); // query SQL server directly string query = @"with cteChildren as (select ID, TemplateID from [dbo].[Items] where ID = CAST(@itemId AS UNIQUEIDENTIFIER) union all select e.ID, e.TemplateID from cteChildren cte inner join [dbo].[Items] e on cte.ID = e.ParentID), cteSubItems as ( select ItemId from SharedFields where FieldId = '12C33F3F-86C5-43A5-AEB4-5598CEC45116' and Value like '%' + @templateId + '%' union all select sf.ItemId from SharedFields sf inner join cteSubItems ic on sf.Value like '%' + CAST(ic.ItemId as varchar(36)) + '%' where sf.FieldId = '12C33F3F-86C5-43A5-AEB4-5598CEC45116' ) select cteChild.ID from (select ItemId from cteSubItems union select @templateId) cteSit inner join cteChildren cteChild on cteSit.ItemId = cteChild.TemplateID"; Sitecore.Data.DataProviders.Sql.DataProviderReader reader = dataApi.CreateReader(query, "itemId", item.ID.ToString().Trim(new[] { '{', '}' }), "templateId", templateId.ID.ToString().Trim(new[] { '{', '}' })); // return to the content API to get the actual items (therefore also applies security) List<Item> result = new List<Item>(); while (reader.Read()) { Sitecore.Data.ID itemId = new Sitecore.Data.ID(reader.InnerReader.GetGuid(0)); if (itemId != item.ID) { Item descItem = Sitecore.Context.Database.GetItem(itemId); if (descItem != null) { result.Add(descItem); } } } return result; } public string BenchMark(Func<int> func, int times) { DateTime start = DateTime.Now; int lastResult = -1; for (int i = 0; i < times; i++) { lastResult = func(); } return string.Format("{0}<br />(performed {1} times, and took an average of {2} milliseconds)", lastResult, times, Math.Round(((DateTime.Now - start).TotalMilliseconds / times))); } public void Page_Load(object sender, EventArgs e) { DataBind(); } private Item MyItem { get { return Sitecore.Context.Database.GetRootItem(); } } private Sitecore.Data.ID FileTemplateId { get { return new Sitecore.Data.ID("{962B53C4-F93B-4DF9-9821-415C867B8903}"); } } private bool InheritsFromTemplate(TemplateItem templateItem, Sitecore.Data.ID templateId) { return templateItem.ID == templateId || (templateItem.BaseTemplates != null && templateItem.BaseTemplates .Where(baseTempl => InheritsFromTemplate(baseTempl, templateId)).Count() > 0); } </script> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>Test direct SQL server database access</title> </head> <body> <ol> <li>Count all descendants (using sitecore content API) = <%# BenchMark(() => MyItem.Axes.GetDescendants().Count(), 50) %></li> <li>Count all descendants (directly with the database, disregards security) = <%# BenchMark(() => GetDescendantsCount(MyItem), 50)%></li> <li>Get all descendants that inherit from 'File' (using sitecore content API) = <%# BenchMark(() => MyItem.Axes.GetDescendants() .Where(desc => InheritsFromTemplate(desc.Template, FileTemplateId)).Count(), 50) %></li> <li>Get all descendants that inherit from 'File' (directly with the database) = <%# BenchMark(() => GetDescendantsInheritingTemplate(MyItem, new Sitecore.Data.TemplateID(FileTemplateId)).Count(), 50)%></li> </ol> </body> </html> |