Quantcast
Channel: Knifecore
Viewing all articles
Browse latest Browse all 21

Bypass the content API for better performance

$
0
0

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:

  1. Persist a more suitable representation of your data. E.g.: output caching, Sitecore caching, Lucene indexing.
  2. 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:

  1. More storage/memory is needed.
  2. 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:

  1. Relies on the underlying implementation. You may have to change the implementation with future upgrades.
  2. 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>

Viewing all articles
Browse latest Browse all 21

Trending Articles