A brief look at the Sitecore HistoryManager

In my previous post I explored a couple of options to tap into publishing events in Sitecore. While this gave you a foothold into the publishing processes, chances are you’ll want a little more information in order to meet your requirements. Enter the HistoryManager.

A lot of what you’ll read here came from a post by Alex Shyba. The link is below and it’s definitely worth reading if you’re using the History table. I’m hoping to add my own perspective here and add a little more visibility to this feature of Sitecore.

Two quick notes before we get too far. I’m using Sitecore’s HistoryManager in the context of a publishing event because that’s where I’ve seen the most utility from it but there is nothing preventing you from using it in a different manner. If you do end up using this on your content delivery servers you’ll need to add the HistoryEngine to your web database. I’ll provide a sample config file below to accomplish that.

Since the history table will contain every action going back to when it was last cleaned up our first task will be to figure out when the last time we checked the history was. We’ll then pull all of the history entries after that time and record the current time.

The easiest way to store the time the history engine was last processed is by using the properties table of your target database. This can be accessed with the Properties method of the database. This is a handy little trick I didn’t know about until recently that allows you to save a key/value. Want to know more? See: [Sitecore_web].[dbo].[Properties])

[sourcecode language="csharp"] var now = DateTime.UtcNow;

DateTime fromDate = string.IsNullOrWhiteSpace(database.Properties[LastUpdateTimeKey]) ?

DateTime.MinValue :

DateUtil.ParseDateTime(database.Properties[LastUpdateTimeKey], DateTime.MinValue);

[/sourcecode]

Now that we know when we last checked the database let’s grab every record from then until now. We’ll do this using the GetHistoryMethod of the HistoryManager. This will give us an enumeration of HistoryEntries to work with.

[sourcecode language="csharp"]var historyEntries = HistoryManager.GetHistory(database, fromDate, now)

.Where(x => x.Category == HistoryCategory.Item);

[/sourcecode]

We’ll want to limit this set to those that are in the item category. Intellisense tells me the other option is “Unknown” which scares me, let’s just stay away.

If you take a peek at one of the HistoryEntries you’ll see that this entry contains an item ID, language and version number. These, combined with the database should be everything you need to uniquely identify a piece of content.

There’s also some additional data that could come in handy, such as “Action” which will tell you if this is new, being updated or getting removed.

Finally we’ll want to write the current datetime back into the database properties.

[sourcecode language="csharp"]database.Properties[LastUpdateTimeKey] = DateUtil.ToIsoDate(now, true);

[/sourcecode]

What’s really slick about this is that it gives us a better view into the results of the publish operation. As an example if we were to smart publish a new item we’ll get a history entry for its creation. If we were to smart publish again (with no changes) no history entry would be made. History entries are also created for each item published so that we’re quickly able to identify items whose ancestors were the root target of the publish operation.

Ok, on to the show. Since I have the code handy I’m going to just hijack what was used in my previous post for the publish:complete event. For reference I’ll be publishing a home item with on child item. Both items are in the final state of workflow and both items have an English version as well as a French (Canada) version.

Adding the following code:

[sourcecode language="csharp"]Log.Debug(string.Format("Publishing is complte. Found {0} history entries!", historyEntries.Count()), this);

foreach (var historyEntry in historyEntries)

{

Log.Debug(string.Format("History Entry: Item: {0}, Language: {1}, Version: {2}, Action: {3}", historyEntry.ItemPath, historyEntry.ItemLanguage, historyEntry.ItemVersion, historyEntry.Action), this);

}

[/sourcecode]

We see the following:

Initial smart publish to English.

ManagedPoolThread #3 11:10:26 DEBUG Publishing is complte. Found 4 history entries!
ManagedPoolThread #3 11:10:26 DEBUG History Entry: Item: /sitecore/content/Home, Language: en, Version: 1, Action: Created
ManagedPoolThread #3 11:10:26 DEBUG History Entry: Item: /sitecore/content/Home, Language: en, Version: 1, Action: Saved
ManagedPoolThread #3 11:10:26 DEBUG History Entry: Item: /sitecore/content/Home/Sample Item, Language: en, Version: 1, Action: Created
ManagedPoolThread #3 11:10:26 DEBUG History Entry: Item: /sitecore/content/Home/Sample Item, Language: en, Version: 1, Action: Saved

Smart publish to English & French (Canada)

ManagedPoolThread #16 11:11:20 DEBUG Publishing is complte. Found 2 history entries!
ManagedPoolThread #16 11:11:20 DEBUG History Entry: Item: /sitecore/content/Home, Language: fr-CA, Version: 1, Action: Saved
ManagedPoolThread #16 11:11:20 DEBUG History Entry: Item: /sitecore/content/Home/Sample Item, Language: fr-CA, Version: 1, Action: Saved

2nd Smart publish to English & French (Canada)

ManagedPoolThread #6 11:12:08 DEBUG Publishing is complte. Found 0 history entries!

And finally, republish to English & French(Canada)

ManagedPoolThread #14 11:12:40 DEBUG Publishing is complte. Found 4 history entries!
ManagedPoolThread #14 11:12:40 DEBUG History Entry: Item: /sitecore/content/Home, Language: en, Version: 1, Action: Saved
ManagedPoolThread #14 11:12:40 DEBUG History Entry: Item: /sitecore/content/Home/Sample Item, Language: en, Version: 1, Action: Saved
ManagedPoolThread #14 11:12:40 DEBUG History Entry: Item: /sitecore/content/Home, Language: fr-CA, Version: 1, Action: Saved
ManagedPoolThread #14 11:12:40 DEBUG History Entry: Item: /sitecore/content/Home/Sample Item, Language: fr-CA, Version: 1, Action: Saved

Reference:

If you’re using the HistoryManager you should read this.

http://sitecoreblog.alexshyba.com/2009/12/get-all-published-items-in-sitecore.html

And be aware of this.

http://stackoverflow.com/questions/16908553/items-in-sitecore-history-table-have-incorrect-creation-date

AddHistoryToWebDatabase.config, as promised

[sourcecode language="xml"]<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">

<sitecore>

<databases>

<database id="web" singleInstance="true" type="Sitecore.Data.Database, Sitecore.Kernel">

<Engines.HistoryEngine.Storage>

<obj type="Sitecore.Data.$(database).$(database)HistoryStorage, Sitecore.Kernel">

<param connectionStringName="$(id)" />

<EntryLifeTime>30.00:00:00</EntryLifeTime>

</obj>

</Engines.HistoryEngine.Storage>

<Engines.HistoryEngine.SaveDotNetCallStack>false</Engines.HistoryEngine.SaveDotNetCallStack>

</database>

</databases>

</sitecore>

</configuration>

[/sourcecode]