0

For a Sitecore 8.2 solution I have a save event handler that populates a field on an item of several different templates based upon items that refer to its child items via the Link Database. That logic works correctly but I am facing two different issues depending on the environment.

  1. In my local and test server environment, everything works as expected, however, in order to see the new values on the item I have to save twice. Stepping through the code shows that the field is populated the first time but is then cleared after the EndEdit() line is run. When I save again the value is preserved after the EndEdit().

After looking at this more I found that if I save once and then clear all the caches and come back to the item the values show up, indicating that it is a caching issue. Occasionally it will show the values on the 1st save but this only happens maybe 30% of the time. However as shown in the code, cacheManager is set to false when I actually edit the item, but it does not prevent the issue.

I'm looking for a solution so it always shows the updated value to the user on item save. Is there a way to clear a specific cache or even this specific item in that cache or is there a different way to handle this?

  1. On a dev server environment I notice different behavior. I can save multiple times and clear the cache and the field is not populated. It is not until I change the value on a different field in the content editor and save that it updates. I often have to update that different field and save twice for this to actually update, or I need to edit the other field once and then clear the cache for it show the updated field.

I am not sure why this behavior is different as the environments are fairly similar, with the biggest difference being that the dev server is a standalone instance (no CDs) just like my local machine while the test environment scalable (CM and CD servers).

Update 1

I have updated the code below at the end after CacheManager.Enabled = true; It now includes a clearing of the master database items cache. Doing this appears to allow the item to display the updated values immediately on local and test environment but not the dev dev environment. However, this clears the entire item cache. I would prefer to only clear the item being updated in the cache instead but do not see any methods available with ICacheInfo to do this.

public class ProductModelAccComputedSaveHandler
{
    protected void OnItemSaved(object sender, EventArgs args)
    {
        if (args == null) return;

        Item savedItem = Event.ExtractParameter(args, 0) as Item;

        if (savedItem == null || savedItem.Database.Name.ToLower() != "master") return;

        // List of product model template IDs to ensure this logic only applies to models
        List<ID> productTemplateIds = new List<ID>
        {
            new ID("{2E8728B0-22C0-4805-A87D-22FFEEEB0537}"),
            new ID("{B9D9508E-24F7-4EDC-94BD-6CF494CDF12C}"),
            new ID("{A5CFE2B3-3EB8-4B7C-9BC9-0126EA3417E6}"),
            new ID("{9417E6E9-8A84-4EE5-B5B2-A89626C579FA}"),
        };

        if (productTemplateIds.Contains(savedItem.TemplateID))
        {
            List<string> accessoryIDS = new List<string>();

            foreach (Sitecore.Data.Items.Item modelOption in savedItem.Children)
            {
                var modelOptionReferrers = ExtensionMethods.SitecoreItemExtensions.GetReferrers(modelOption);
                var modelOptionRefAccessories = Array.FindAll(modelOptionReferrers, c => c != null && c.TemplateID == new ID("{1385E0E6-3DA1-4636-A69C-2A5A8B299155}")).ToList();

                foreach (var accessory in modelOptionRefAccessories)
                {
                    accessoryIDS.Add(accessory.ID.ToString());
                }
            }
      
            accessoryIDS = accessoryIDS.Distinct().ToList();

            Sitecore.Data.Fields.MultilistField assignedAccessories = savedItem.Fields["Accessories Assigned to This Product"];

            if (accessoryIDS != null && accessoryIDS.Count != 0)
            {
                CacheManager.Enabled = false;
                using (new Sitecore.SecurityModel.SecurityDisabler())
                {
                    using (new EventDisabler())
                    {
                        savedItem.Editing.BeginEdit();

                        try
                        {
                            // clear field values so this event handler does not just keep adding and adding values
                            assignedAccessories.Value = string.Empty;

                            foreach (var accessory in accessoryIDS)
                            {
                                assignedAccessories.Add(accessory);
                            }

                            savedItem.Editing.EndEdit();
                        }
                        catch (Exception ex)
                        {
                            savedItem.Editing.CancelEdit();
                            Log.Error("Error updating computed field:" + ex, this);
                        }
                        CacheManager.Enabled = true;

                        var masterCaches = CacheManager.GetAllCaches();
                        var masterItemCache = masterCaches.SingleOrDefault(c => c.Name == "master[items]");
                        masterItemCache.Clear();
                    }
                }
            }
        }
    }
}
4
  • 1
    Have you tried changing your event from item:saved to item:saving? Oct 15 '20 at 18:15
  • Yes, that did not resolve the issue. I have added an update where I can clear the entire master database item cache but would prefer to clear that item individually.
    – Chris
    Oct 15 '20 at 20:37
  • 1
    Remove EventDisabler()) from your method and remove clearing caches. EventDisabler pretty much tells Sitecore to only change data in database but ignore refreshing of all the caches Oct 16 '20 at 6:52
  • Thanks Marek, I removed the EventDisabler and replaced it with a static list where I add the item if the logic runs and then checks it went the EndEdit causes the event to fire again. I had added it to prevent the infinite loop but like you mentioned that causes the cache to not be cleared but this change resolves this.
    – Chris
    Oct 16 '20 at 16:40
0

I was able to resolve this issue by changing the approach to how I prevented the infinite loop that would be caused by this save event handler as the act of saving the updated value would trigger this event handler.

Previously, I used the EventDisabler() around the BeginEdit() EndEdit(). However, this causes the cache to not be cleared for the item which results in the updated value not being displayed to the user.

Using a static list, I add the item being processed if it passes the set of checks, the most important being the template check, so that when the item is saved and the event handler is hit again there is check to see if the item in the event matches the item in the list and it does to exit the handler. At the end I remove the item from the list as after it exits the handler from EndEdit() it will hit the finally block.

I found this approach by this post by John West from a few years ago: https://community.sitecore.net/technical_blogs/b/sitecorejohn_blog/posts/repost-intercepting-item-updates-with-sitecore

For completeness this method allows you to remove a specific item from the item cache CacheManager.GetItemCache(Sitecore.Context.Database).RemoveItem(item.ID);

Finally, thanks to Marek for his suggestion to remove the EventDisabler().

public class ProductModelAccComputedSaveHandler
{
    private static readonly SynchronizedCollection<Sitecore.Data.ID> _inProcess = new SynchronizedCollection<Sitecore.Data.ID>();

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

    Item savedItem = Event.ExtractParameter(args, 0) as Item;

    if (savedItem == null || savedItem.Database.Name.ToLower() != "master") return;
    
    // check to see if this item was already just saved, if so exit to prevent infinite loop.  
    if (_inProcess.Contains(savedItem.ID)) return;

    // List of product model template IDs to ensure this logic only applies to models
    List<ID> productTemplateIds = new List<ID>
    {
        new ID("{2E8728B0-22C0-4805-A87D-22FFEEEB0537}"),
        new ID("{B9D9508E-24F7-4EDC-94BD-6CF494CDF12C}"),
        new ID("{A5CFE2B3-3EB8-4B7C-9BC9-0126EA3417E6}"),
        new ID("{9417E6E9-8A84-4EE5-B5B2-A89626C579FA}"),
    };

    if (productTemplateIds.Contains(savedItem.TemplateID))
    {
        List<string> accessoryIDS = new List<string>();

        foreach (Sitecore.Data.Items.Item modelOption in savedItem.Children)
        {
            var modelOptionReferrers = ExtensionMethods.SitecoreItemExtensions.GetReferrers(modelOption);
            var modelOptionRefAccessories = Array.FindAll(modelOptionReferrers, c => c != null && c.TemplateID == new ID("{1385E0E6-3DA1-4636-A69C-2A5A8B299155}")).ToList();

            foreach (var accessory in modelOptionRefAccessories)
            {
                accessoryIDS.Add(accessory.ID.ToString());
            }
        }
  
        accessoryIDS = accessoryIDS.Distinct().ToList();

        Sitecore.Data.Fields.MultilistField assignedAccessories = savedItem.Fields["Accessories Assigned to This Product"];

        if (accessoryIDS != null && accessoryIDS.Count != 0)
        {
            using (new Sitecore.SecurityModel.SecurityDisabler())
            {
                using (new EventDisabler())
                {
                     _inProcess.Add(savedItem.ID);
                    
                    savedItem.Editing.BeginEdit();

                    try
                    {
                        // clear field values so this event handler does not just keep adding and adding values
                        assignedAccessories.Value = string.Empty;

                        foreach (var accessory in accessoryIDS)
                        {
                            assignedAccessories.Add(accessory);
                        }

                        savedItem.Editing.EndEdit();
                    }
                    catch (Exception ex)
                    {
                        savedItem.Editing.CancelEdit();
                        Log.Error("Error updating computed field:" + ex, this);
                    }
                    finally
                    {
                        _inProcess.Remove(savedItem.ID);
                    }
                }
            }
        }
    }
}
   

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.