Managing Items as Resources in Sitecore xmCloud

Managing Items as Resources in Sitecore xmCloud
Managing Items as Resources in Sitecore xmCloud

As you may know for a while Sitecore uses Items as resources for deployment (IAR). Sitecore uses Protobuf to generate static read-only serialized items.

Protocol Buffers (a.k.a., protobuf) are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data. You can learn more about it in protobuf's documentation. You may also want to take a look at protobuf-net which is the .net implementation of the Protobuf, and I must say it is quite fast.

Protobuf (IARs) are deployed inside "App_Data/items" and "sitecore modules/items" folders. The Sitecore uses CompositeDataProvider to load IARs items from the disk:

<database id="core" type="Sitecore.Data.DefaultDatabase, Sitecore.Kernel" singleInstance="true">
    <param desc="name">$(id)</param>
    <connectionStringName>$(id)</connectionStringName>
    <icon>Images/database_core.png</icon>
    <dataProviders hint="list:AddDataProvider">
        <dataProvider type="Sitecore.Data.DataProviders.CompositeDataProvider, Sitecore.Kernel">
            <param hint="list" desc="readOnlyDataProviders">
                <protobufItems
                    type="Sitecore.Data.DataProviders.ReadOnly.Protobuf.ProtobufDataProvider, Sitecore.Kernel">
                    <filePaths hint="list">
                        <filePath>/App_Data/items/$(id)</filePath>
                        <modulesFilePath>/sitecore modules/items/$(id)</modulesFilePath>
                    </filePaths>
                </protobufItems>
            </param>
        </dataProvider>
    </dataProviders>
</database>

IARs have their own benefits, especially for the Upgrade process or deploying the Sitecore hotfixes.

The Item load priorities are the following:

  • Main databases from "App_Data/items" folder
  • Modules from "sitecore modules/items"
  • Your serialized deployed items from "App_Data/items" folder
  • Items saved directly in the Database

And now comes the challenges.

Challenges of IAR deployment

You may face a few Challenges with the IARs, few examples are:

  • You don't know what is inside the IAR
  • You may face a racing issue, if you have overwritten another IAR item
  • The changes in the database may affect your deployment.

For the first challenge, I have provided a tool that allows you to browse the IAR items and see what is inside, in the Next blog post I will introduce it to you.

We had the second challenge in the xmCloud with our serialized items and Sitecore fixed it. Keep in mind in some cases it should be treated as a red flag for you, maybe you need to rethink your work. Modules usually should not be overridden.

The third challenge is the main topic of this post, so let us talk about it.

Resetting IAR items

We have always the issue that someone changes some of the IAR items on for example, the Staging or Production environment, what happens is, that item, for example, rendering/template or partial design will be saved in the database.
The next deployment will update the IAR items on the disk but because the Items from the database have higher load priority, basically the changes from deployed IAR items will be ignored. So we need a way to reset these items or remove them from the database.

There are a few ways to do this:

  • If possible delete the item directly using content-editor, this won't work if the item has dependencies, for example, a Template that has pages based on it.
  • In the early days of the xmCloud we did it using a SQL script to remove the IAR items from Database, and I do not recommend it.
  • Sitecore added the reset functionality to the CLI
  • Using Powershell

Deleting in Content Editor

In some cases, for resetting IAR items you can simply use the content editor:
delete

and it is important to leave the links as they are, because you are not really deleting the item, you are just removing it from the database.
After the deletion, you will see the item is restored from resources:
delete restored

** Note: Make sure the item is an IAR item (Use Powershell reports from this blog or check your serializations). Be specially careful if you have a subitems which are not serialized.

Reset using Sitecore CLI

Sitecore cli itemres API has a cleanup command which can be used with --what-if to show you an overview of what is going to happen:

dotnet sitecore itemres cleanup --what-if --force -n staging

Running it will clean up the changes in the database, you may need to be careful if you have customer contents in your serialization (ex. Home item).

** Downside: you can see or clean only the overridden items included in the serializations. If someone has changes O.O.B items, you may not notice them.

Reporting and Reseting using Powershell

The Sitecore API's have some classes which help you to figure out the Item locations (IAR or SQL).

Using those API, I have created some Powershell functions which help you along the way to manage the IAR on your instances.
IAR Powershell

The full report is slow and very resource-consuming, i may refactor it later to use solr search. It will check these paths:

  • /Templates
  • /System
  • /Layout
  • /Content

And based on the result it will generate the Report:
Powershell Full IAR Report

And Subtree Report will check the overridden items on the current subtree.

Clean-up scripts and challenges

As mentioned before clean-up could get tricky. In the case of the templates, if they have pages created based on them, the Sitecore API doesn't allow you to just delete them.

The only way I could find, was to delete them directly from the database:

# Remove given Item From Database
function Invoke-CleanupIARItem {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [Item]$Item
    )
    begin {
        Write-Verbose "Cmdlet Invoke-CleanupIARItem - Begin"
        Import-Function Test-InDelegatedArea
        Import-Function Test-IARItem
    }
    process {
        Write-Verbose "Cmdlet Invoke-CleanupIARItem - Process"
        if (Test-IARItem -Item $Item) {
            $sourceCode = @"
    namespace SitecoreDatabaseCleanup
    {
        using System;
        using System.Data.SqlClient;
        public class CleanDatabase
        {
            public static string DeleteItemFromDB(Sitecore.Data.Items.Item item)
            {
                string connectionString = Sitecore.Configuration.Settings.GetConnectionString("master");
                string itemId = item.ID.Guid.ToString();
                var result = DeleteFields(itemId, connectionString);
    
                if (string.IsNullOrWhiteSpace(result))
                {
                    result = DeleteItem(itemId, connectionString);
                }
    
                return string.IsNullOrWhiteSpace(result) ? "Success" : result;
            }
            private static string DeleteFields(string itemId, string connectionString)
            {
                var result = Delete(connectionString, GetFieldDeleteCommand("VersionedFields", itemId));
                if (!string.IsNullOrWhiteSpace(result))
                {
                    return result;
                }
                result = Delete(connectionString, GetFieldDeleteCommand("UnversionedFields", itemId));
                if (!string.IsNullOrWhiteSpace(result))
                {
                    return result;
                }
                result = Delete(connectionString, GetFieldDeleteCommand("SharedFields", itemId));
                if (!string.IsNullOrWhiteSpace(result))
                {
                    return result;
                }
    
                return string.Empty;
            }
    
            private static string DeleteItem(string itemId, string connectionString)
            {
                return Delete(connectionString, string.Concat("DELETE FROM [dbo].[Items] where [Id]='", itemId, "'"));
            }
    
            private static string GetFieldDeleteCommand(string tableName, string itemId)
            {
                return string.Concat("DELETE FROM [dbo].[", tableName, "] where [ItemId]='", itemId, "'");
            }
            private static string Delete(string connectionString, string deleteCommand)
            {
    
                try
                {
                    using (var cnn = new SqlConnection(connectionString))
                    using (SqlCommand cmd = new SqlCommand(deleteCommand, cnn))
                    {
                        cnn.Open();
                        cmd.ExecuteNonQuery();
                        cnn.Close();
                    }
                }
                catch (Exception e)
                {
                    return string.Concat(e.Message, "\r\n", e.StackTrace);
                }
    
                return string.Empty;
            }
        }
    }
"@
    
            Add-Type -TypeDefinition $SourceCode -Language CSharp -ReferencedAssemblies System.Data, Sitecore.Kernel  -PassThru | Out-Null
            $result = [SitecoreDatabaseCleanup.CleanDatabase]::DeleteItemFromDB($item)
            if ($result -ne "Success") {
                throw $result
            }
        }
    }
    
    end {
        Write-Verbose "Cmdlet Invoke-CleanupIARItem - End"
    }
}

The script will check if the item is IAR, it will delete it using the Sql-Command.
If you use Sitecore API to delete an Item, it will also delete the children, which could cause a content loss.
Read the source on github, test it, and use it at your own risk!

Conclusion

Sitecore IAR has resolved quite a few problems and made deployment much easier, the Sitecore CLI is also actively evolving and will get more features in the future.

Pwershell features are not xmCloud specific and can be used with every Sitecore version. Even if you do not want to use clean-up scripts, the reporting will help you to figure out which O.O.B or custom IAR items are overridden.

The Source code and Sitecore package are on Github .