Home » IDTable

Category Archives: IDTable

Augment Functionality in Sitecore Using the Decorator Design Pattern

Over the past few days, I’ve been trying to come up with a good idea for a blog post showing the usage of the Decorator design pattern in Sitecore.

During this time of cogitation, I was having difficulties coming up with a good example despite having had used this pattern in Sitecore on many past projects — I can’t really share those solutions since they are owned by either previous employers or clients.

However, I finally had an “EUREKA!” moment after John West — CTO of Sitecore USA — wrote a blog post earlier today where he shared an <httpRequestBegin> pipeline processor which redirects to a canonical URL for an Item.

So, what exactly did I come up with?

I built the following example which simply “decorates” the “out of the box” ItemResolver — Sitecore.Pipelines.HttpRequest.ItemResolver in Sitecore.Kernel.dll — which is used as an <httpRequestBegin> pipeline processor to figure out what the context Item should be from the URL being requested by looking for an entry in the IDTable in Sitecore (note: this idea is adapted from a blog post that Alex Shyba — Director of Platform Innovation and Engineering at Sitecore — wrote a few years ago):

using Sitecore;
using Sitecore.Data;
using Sitecore.Data.IDTables;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Pipelines.HttpRequest;

namespace Sitecore.Sandbox.Pipelines.HttpRequest
{
    public class IDTableItemResolver : HttpRequestProcessor
    {
        private string Prefix { get; set; }

        private HttpRequestProcessor InnerProcessor { get; set; }

        public override void Process(HttpRequestArgs args)
        {
            Assert.ArgumentNotNull(args, "args");
            AssertProperties();
            
            Item item = GetItem(args.Url.FilePath);
            if (item == null)
            {
                InnerProcessor.Process(args);       
                return;
            }

            Context.Item = item; 
        }

        protected virtual void AssertProperties()
        {
            Assert.IsNotNullOrEmpty(Prefix, "Prefix", "Prefix must be set in configuration!");
            Assert.IsNotNull(InnerProcessor, "InnerProcessor", "InnerProcessor must be set in configuration!");
        }

        protected virtual Item GetItem(string url)
        {
            IDTableEntry entry = IDTable.GetID(Prefix, url);
            if (entry == null || entry.ID.IsNull)
            {
                return null;
            }

            return GetItem(entry.ID);
        }

        protected Item GetItem(ID id)
        {
            Database database = GetDatabase();
            if (database == null)
            {
                return null;
            }

            return database.GetItem(id);
        }

        protected virtual Database GetDatabase()
        {
            return Context.Database;
        }
    }
}

What is the above class doing? It’s basically seeing if it can find an Item for the passed relative URL — this is passed via the FilePath property of the Url property of the HttpRequestArgs instance taken in by the Process() method — by delegating to a method that looks up an entry in the IDTable for the URL — the URL would be the key into the IDTable — and return the Item from the context database if an entry is found. If no entry is found, it just returns null.

If null is returned, that pretty much means there is no entry in the IDTable for the given relative URL so a delegation to the Process() method of the InnerProcessor is needed in order to preserve “out of the box” Sitecore functionality for Item URL resolution.

I then replaced the “out of the box” ItemResolver with the above in the following patch include configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor patch:instead="*[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']" 
                   type="Sitecore.Sandbox.Pipelines.HttpRequest.IDTableItemResolver, Sitecore.Sandbox">
          <Prefix>UrlRewrite</Prefix>
          <InnerProcessor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />
        </processor>  
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

In the above configuration file, we are setting the “out of the box” ItemResolver to be injected into the class above so that its Process() method can be “decorated”.

Let’s see this in action!

Let’s try this out with the following page Item:

mario-content-editor

In order to see the above <httpRequestBegin> pipeline processor in action, I had to add an entry into my IDTable — let’s make pretend an UrlRewrite module on the Sitecore Marketplace added this entry for us:

url-rewrite-id-table

I loaded up another browser window; navigated to the relative URL specified in the IDTable entry; and then saw the following:

resolved-url

As you can see, it worked.

We can also navigate to the same page using its true URL — the one resolved by Sitecore “out of the box”:

mario-resolved

The above worked because the inner processor resolved it.

Let’s now go to a completely different page Item altogether. Let’s use this one:

cat-page-five-sitecore

As you can see, that also worked:

cat-page-five

If you have any thoughts on this, or have other ideas around using the Decorator pattern in Sitecore, please share in a comment.

Synchronize IDTable Entries Across Multiple Sitecore Databases Using a Custom publishItem Pipeline Processor

In a previous post I showed a solution that uses the Composite design pattern in an attempt to answer the following question by Sitecore MVP Kyle Heon:

Although I enjoyed building that solution, it isn’t ideal for synchronizing IDTable entries across multiple Sitecore databases — entries are added to all configured IDTables even when Items might not exist in all databases of those IDTables (e.g. the Sitecore Items have not been published to those databases).

I came up with another solution to avoid the aforementioned problem — one that synchronizes IDTable entries using a custom <publishItem> pipeline processor, and the following class contains code for that processor:

using System.Collections.Generic;
using System.Linq;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.IDTables;
using Sitecore.Diagnostics;
using Sitecore.Publishing.Pipelines.PublishItem;

namespace Sitecore.Sandbox.Pipelines.Publishing
{
    public class SynchronizeIDTables : PublishItemProcessor
    {
        private IEnumerable<string> _IDTablePrefixes;
        private IEnumerable<string> IDTablePrefixes
        {
            get
            {
                if (_IDTablePrefixes == null)
                {
                    _IDTablePrefixes = GetIDTablePrefixes();
                }

                return _IDTablePrefixes;
            }
        }

        private string IDTablePrefixesConfigPath { get; set; }

        public override void Process(PublishItemContext context)
        {
            Assert.ArgumentNotNull(context, "context");
            Assert.ArgumentNotNull(context.PublishOptions, "context.PublishOptions");
            Assert.ArgumentNotNull(context.PublishOptions.SourceDatabase, "context.PublishOptions.SourceDatabase");
            Assert.ArgumentNotNull(context.PublishOptions.TargetDatabase, "context.PublishOptions.TargetDatabase");
            IDTableProvider sourceProvider = CreateNewIDTableProvider(context.PublishOptions.SourceDatabase);
            IDTableProvider targetProvider = CreateNewIDTableProvider(context.PublishOptions.TargetDatabase);
            RemoveEntries(targetProvider, GetAllEntries(targetProvider, context.ItemId));
            AddEntries(targetProvider, GetAllEntries(sourceProvider, context.ItemId));
        }

        protected virtual IDTableProvider CreateNewIDTableProvider(Database database)
        {
            Assert.ArgumentNotNull(database, "database");
            return Factory.CreateObject(string.Format("IDTable[@id='{0}']", database.Name), true) as IDTableProvider;
        }

        protected virtual IEnumerable<IDTableEntry> GetAllEntries(IDTableProvider provider, ID itemId)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(itemId), "itemId", "itemId cannot be null or empty!");
            List<IDTableEntry> entries = new List<IDTableEntry>();
            foreach(string prefix in IDTablePrefixes)
            {
                IEnumerable<IDTableEntry> entriesForPrefix = provider.GetKeys(prefix, itemId);
                if (entriesForPrefix.Any())
                {
                    entries.AddRange(entriesForPrefix);
                }
            }

            return entries;
        }

        private static void RemoveEntries(IDTableProvider provider, IEnumerable<IDTableEntry> entries)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentNotNull(entries, "entries");
            foreach (IDTableEntry entry in entries)
            {
                provider.Remove(entry.Prefix, entry.Key);
            }
        }

        private static void AddEntries(IDTableProvider provider, IEnumerable<IDTableEntry> entries)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentNotNull(entries, "entries");
            foreach (IDTableEntry entry in entries)
            {
                provider.Add(entry);
            }
        }

        protected virtual IEnumerable<string> GetIDTablePrefixes()
        {
            Assert.ArgumentNotNullOrEmpty(IDTablePrefixesConfigPath, "IDTablePrefixConfigPath");
            return Factory.GetStringSet(IDTablePrefixesConfigPath);
        }
    }
}

The Process method above grabs all IDTable entries for all defined IDTable prefixes — these are pulled from the configuration file that is shown later on in this post — from the source database for the Item being published, and pushes them all to the target database after deleting all preexisting entries from the target database for the Item (the code is doing a complete overwrite for the Item’s IDTable entries in the target database).

I also added the following code to serve as an item:deleted event handler (if you would like to learn more about events and their handlers, check out John West‘s post about them, and also take a look at this page on the
Sitecore Developer Network (SDN)) to remove entries for the Item when it’s being deleted:

using System;
using System.Collections.Generic;
using System.Linq;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.Events;
using Sitecore.Data.IDTables;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Events;

namespace Sitecore.Sandbox.Data.IDTables
{
    public class ItemEventHandler
    {
        private IEnumerable<string> _IDTablePrefixes;
        private IEnumerable<string> IDTablePrefixes
        {
            get
            {
                if (_IDTablePrefixes == null)
                {
                    _IDTablePrefixes = GetIDTablePrefixes();
                }

                return _IDTablePrefixes;
            }
        }

        private string IDTablePrefixesConfigPath { get; set; }

        protected void OnItemDeleted(object sender, EventArgs args)
        {
            if (args == null)
            {
                return;
            }

            Item item = Event.ExtractParameter(args, 0) as Item;
            if (item == null)
            {
                return;
            }

            DeleteItemEntries(item);
        }

        private void DeleteItemEntries(Item item)
        {
            Assert.ArgumentNotNull(item, "item");
            IDTableProvider provider = CreateNewIDTableProvider(item.Database.Name);
            foreach (IDTableEntry entry in GetAllEntries(provider, item.ID))
            {
                provider.Remove(entry.Prefix, entry.Key);
            }
        }

        protected virtual IEnumerable<IDTableEntry> GetAllEntries(IDTableProvider provider, ID itemId)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentCondition(!ID.IsNullOrEmpty(itemId), "itemId", "itemId cannot be null or empty!");
            List<IDTableEntry> entries = new List<IDTableEntry>();
            foreach (string prefix in IDTablePrefixes)
            {
                IEnumerable<IDTableEntry> entriesForPrefix = provider.GetKeys(prefix, itemId);
                if (entriesForPrefix.Any())
                {
                    entries.AddRange(entriesForPrefix);
                }
            }

            return entries;
        }

        private static void RemoveEntries(IDTableProvider provider, IEnumerable<IDTableEntry> entries)
        {
            Assert.ArgumentNotNull(provider, "provider");
            Assert.ArgumentNotNull(entries, "entries");
            foreach (IDTableEntry entry in entries)
            {
                provider.Remove(entry.Prefix, entry.Key);
            }
        }

        protected virtual IDTableProvider CreateNewIDTableProvider(string databaseName)
        {
            return Factory.CreateObject(string.Format("IDTable[@id='{0}']", databaseName), true) as IDTableProvider;
        }

        protected virtual IEnumerable<string> GetIDTablePrefixes()
        {
            Assert.ArgumentNotNullOrEmpty(IDTablePrefixesConfigPath, "IDTablePrefixConfigPath");
            return Factory.GetStringSet(IDTablePrefixesConfigPath);
        }
    }
}

The above code retrieves all IDTable entries for the Item being deleted — filtered by the configuration defined IDTable prefixes — from its database’s IDTable, and calls the Remove method on the IDTableProvider instance that is created for the Item’s database for each entry.

I then registered all of the above in Sitecore using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <events>
      <event name="item:deleted">
        <handler type="Sitecore.Sandbox.Data.IDTables.ItemEventHandler, Sitecore.Sandbox" method="OnItemDeleted">
          <IDTablePrefixesConfigPath>IDTablePrefixes/IDTablePrefix</IDTablePrefixesConfigPath>
        </handler>
      </event>
    </events>
    <IDTable type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
      <patch:attribute name="id">master</patch:attribute>
      <param connectionStringName="master"/>
      <param desc="cacheSize">500KB</param>
    </IDTable>
    <IDTable id="web" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
      <param connectionStringName="web"/>
      <param desc="cacheSize">500KB</param>
    </IDTable>
    <IDTablePrefixes>
      <IDTablePrefix>IDTableTest</IDTablePrefix>
    </IDTablePrefixes>
    <pipelines>
      <publishItem>
        <processor type="Sitecore.Sandbox.Pipelines.Publishing.SynchronizeIDTables, Sitecore.Sandbox">
          <IDTablePrefixesConfigPath>IDTablePrefixes/IDTablePrefix</IDTablePrefixesConfigPath>
        </processor>
      </publishItem>
    </pipelines>
  </sitecore>
</configuration>

For testing, I quickly whipped up a web form to add a couple of IDTable entries using an IDTableProvider for the master database — I am omitting that code for brevity — and ran a query to verify the entries were added into the IDTable in my master database (I also ran another query for the IDTable in my web database to show that it contains no entries):

idtables-before-publish

I published both items, and queried the IDTable in the master and web databases:

idtables-after-publish-both-items

As you can see, both entries were inserted into the web database’s IDTable.

I then deleted one of the items from the master database via the Sitecore Content Editor:

idtables-deleted-from-master

It was removed from the IDTable in the master database.

I then published the deleted item’s parent with subitems:

idtables-published-deletion

As you can see, it was removed from the IDTable in the web database.

If you have any suggestions for making this code better, or have another solution for synchronizing IDTable entries across multiple Sitecore databases, please share in a comment.

Synchronize IDTable Entries Across Multiple Sitecore Databases Using a Composite IDTableProvider

The other day Sitecore MVP Kyle Heon asked:

This tweet got the wheels turning — more like got the hamster wheel spinning in my head — and I began experimenting on ways to synchronize IDTable entries across different Sitecore databases.

In this post, I will show the first solution I had come up with — yes I’ve come up with two solutions to this although no doubt there are more (if you have ideas for other solutions, or have tackled this problem in the past, please share in a comment) — but before I show that solution, I’d like to explain what the IDTable in Sitecore is, and why you might want to use it.

Assuming you’re using SQL Server for Sitecore, the “out of the box” IDTable in Sitecore is a database table that lives in all Sitecore databases — though Sitecore’s prepackaged configuration only points to the IDTable in the master database.

One has the ability to store key/value pairs in this table in the event you don’t want to “roll your own” custom database table — or use some other data store — and don’t want to expose these key/value pair relationships in Items in Sitecore.

The key must be a character string, and the value must be a Sitecore Item ID.

Plus, one has the ability to couple these key/value pairs with “custom data” — this, like the key, is stored in the database as a string which makes it cumbersome around storing complex data structures (having some sort of serialization/deserialization paradigm in place would be required to make this work).

Alex Shyba showed how one could leverage the IDTable for URLs for fictitious products stored in Sitecore in this post, and this article employed the IDTable for a custom Data Provider.

If you would like to know more about the IDTable, please leave a comment, and I will devote a future post to it.

Let’s take a look at the first solution I came up with:

using System;
using System.Collections.Generic;
using System.Xml;

using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Data.IDTables;
using Sitecore.Diagnostics;

using Sitecore.Sandbox.Data.IDTables;

namespace Sitecore.Sandbox.Data.SqlServer
{
    public class CompositeSqlServerIDTable : IDTableProvider
    {
        private IDictionary<string, IDTableProvider> _IDTableProviders;
        private IDictionary<string, IDTableProvider> IDTableProviders
        {
            get
            {
                if (_IDTableProviders == null)
                {
                    _IDTableProviders = new Dictionary<string, IDTableProvider>();
                }

                return _IDTableProviders;
            }
        }

        private string DatabaseName { get; set; }

        public CompositeSqlServerIDTable()
            : this(GetDefaultDatabaseName())
        {
        }

        public CompositeSqlServerIDTable(string databaseName)
        {
            SetDatabaseName(databaseName);
        }

        private void SetDatabaseName(string databaseName)
        {
            Assert.ArgumentNotNullOrEmpty(databaseName, "databaseName");
            DatabaseName = databaseName;
        }

        public override void Add(IDTableEntry entry)
        {
            foreach (IDTableProvider provider in IDTableProviders.Values)
            {
                provider.Add(entry);
            }
        }

        public override IDTableEntry GetID(string prefix, string key)
        {
            return GetContextIDTableProvider().GetID(prefix, key);
        }

        public override IDTableEntry[] GetKeys(string prefix)
        {
            return GetContextIDTableProvider().GetKeys(prefix);
        }

        public override IDTableEntry[] GetKeys(string prefix, ID id)
        {
            return GetContextIDTableProvider().GetKeys(prefix, id);
        }

        protected virtual IDTableProvider GetContextIDTableProvider()
        {
            IDTableProvider provider;
            if (IDTableProviders.TryGetValue(DatabaseName, out provider))
            {
                return provider;
            }

            return new NullIDTable();
        }

        public override void Remove(string prefix, string key)
        {
            foreach (IDTableProvider provider in IDTableProviders.Values)
            {
                provider.Remove(prefix, key);
            }
        }

        protected virtual void AddIDTable(XmlNode configNode)
        {
            if (configNode == null || string.IsNullOrWhiteSpace(configNode.InnerText))
            {
                return;
            }

            IDTableProvider idTable = Factory.CreateObject<IDTableProvider>(configNode);
            if (idTable == null)
            {
                Log.Error("Configuration invalid for an IDTable!", this);
                return;
            }

            XmlAttribute idAttribute = configNode.Attributes["id"];
            if (idAttribute == null || string.IsNullOrWhiteSpace(idAttribute.Value))
            {
                Log.Error("IDTable configuration should have an id attribute set!", this);
                return;
            }

            if(IDTableProviders.ContainsKey(idAttribute.Value))
            {
                Log.Error("Duplicate IDTable id encountered!", this);
                return;
            }

            IDTableProviders.Add(idAttribute.Value, idTable);
        }

        private static string GetDefaultDatabaseName()
        {
            if (Context.ContentDatabase != null)
            {
                return Context.ContentDatabase.Name;
            }

            return Context.Database.Name;
        }
    }
}

The above class uses the Composite design pattern — it adds/removes entries in multiple IDTableProvider instances (these instances are specified in the configuration file that is shown later in this post), and delegates calls to the GetKeys and GetID methods of the instance that is referenced by the database name passed in to the class’ constructor.

The CompositeSqlServerIDTable class also utilizes a Null Object by creating an instance of the following class when the context database does not exist in the collection:

using System.Collections.Generic;

using Sitecore.Data;
using Sitecore.Data.IDTables;

namespace Sitecore.Sandbox.Data.IDTables
{
    public class NullIDTable : IDTableProvider
    {
        public NullIDTable()
        {
        }

        public override void Add(IDTableEntry entry)
        {
            return;
        }

        public override IDTableEntry GetID(string prefix, string key)
        {
            return null;
        }

        public override IDTableEntry[] GetKeys(string prefix)
        {
            return new List<IDTableEntry>().ToArray();
        }

        public override IDTableEntry[] GetKeys(string prefix, ID id)
        {
            return new List<IDTableEntry>().ToArray();
        }

        public override void Remove(string prefix, string key)
        {
            return;
        }
    }
}

The NullIDTable class above basically has no behavior, returns null for the GetID method, and an empty collection for both GetKeys methods. Having a Null Object helps us avoid checking for nulls when performing operations on the IDTableProvider instance when an instance cannot be found in the IDTableProviders Dictionary property — although we lose visibility around the context database not being present in the Dictionary (I probably should’ve included some logging code to capture this).

I then glued everything together using the following configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <IDTable patch:instead="IDTable[@type='Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel']" type="Sitecore.Sandbox.Data.$(database).Composite$(database)IDTable" singleInstance="true">
      <IDTables hint="raw:AddIDTable">
        <IDTable id="core" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
          <param connectionStringName="$(id)"/>
          <param desc="cacheSize">500KB</param>
        </IDTable>
        <IDTable id="master" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
          <param connectionStringName="$(id)"/>
          <param desc="cacheSize">500KB</param>
        </IDTable>
        <IDTable id="web" type="Sitecore.Data.$(database).$(database)IDTable, Sitecore.Kernel" singleInstance="true">
          <param connectionStringName="$(id)"/>
          <param desc="cacheSize">500KB</param>
        </IDTable>
      </IDTables>
    </IDTable>
  </sitecore>
</configuration>

To test the code above, I created the following web form which basically invokes add/remove methods on the instance of our IDTableProvider, and displays the entries in our IDTables after each add/remove operation:

using System;
using System.Collections.Generic;
using System.Linq;

using Sitecore.Configuration;
using Sitecore.Data.IDTables;

namespace Sandbox
{
    public partial class IDTableTest : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            AddEntries();
            ShowEntries();
            RemoveOneEntry();
            ShowEntries();
            RemoveAllEntries();
            ShowEntries();
        }

        private void AddEntries()
        {
            Response.Write("Adding two entries...<br /><br />");
            IDTable.Add("IDTableTest", "/mycoolhomepage1.aspx", Sitecore.Data.ID.Parse("{110D559F-DEA5-42EA-9C1C-8A5DF7E70EF9}"));
            IDTable.Add("IDTableTest", "/someotherpage1.aspx", Sitecore.Data.ID.Parse("{BAB78AE5-8118-4476-B1B5-F8981DAE1779}"));
        }

        private void RemoveOneEntry()
        {
            Response.Write("Removing one entry...<br /><br />");
            IDTable.RemoveKey("IDTableTest", "/mycoolhomepage1.aspx");
        }

        private void RemoveAllEntries()
        {
            Response.Write("Removing all entries...<br /><br />");
            foreach (IDTableEntry entry in IDTable.GetKeys("IDTableTest"))
            {
                IDTable.RemoveKey(entry.Prefix, entry.Key);
            }
        }

        private void ShowEntries()
        {
            ShowEntries("core");
            ShowEntries("master");
            ShowEntries("web");
        }

        private void ShowEntries(string databaseName)
        {
            IDTableProvider provider = CreateNewIDTableProvider(databaseName);
            IEnumerable<IDTableEntry> entries = IDTable.GetKeys("IDTableTest");
            Response.Write(string.Format("{0} entries in IDTable in the {1} database:<br />", entries.Count(), databaseName));
            foreach (IDTableEntry entry in provider.GetKeys("IDTableTest"))
            {
                Response.Write(string.Format("key: {0} id: {1}<br />", entry.Key, entry.ID));
            }
            Response.Write("<br />");
        }

        protected virtual IDTableProvider CreateNewIDTableProvider(string databaseName)
        {
            return Factory.CreateObject(string.Format("//IDTable[@id='{0}']", databaseName), true) as IDTableProvider;
        }
    }
}

When I loaded up the web form above in a browser, I saw that things were working as expected:

idtable-composite-test-run

Although I had fun in writing all of the code above, I feel this isn’t exactly ideal for synchronizing entries in IDTables across multiple Sitecore databases. I cannot see why one would want an entry for a Item in master that has not yet been published to the web database.

If you know of a scenario where the above code could be useful, or have suggestions on making it better, please drop a comment.

In my next post, I will show you what I feel is a better approach to synchronizing entries across IDTables — a solution that synchronizes entries via publishing and Item deletion.

Until next time, have a Sitecorelicious day!