Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jesse Andrews 191 posts 716 karma points c-trib
    Oct 07, 2019 @ 19:00
    Jesse Andrews
    0

    What is the most efficient way to map content to another value in editor model events?

    First a bit of background on the problem I'm trying to solve. I have a number of data types that select nodes as part of their configuration (a number of content pickers and some custom controls). It works great for single site installations, but breaks down for multi site installations, as each site has its own version of the node. I've already setup some logic to map the nodes to their equivalents in the other sites, but I'm running into a performance bottle neck when I go to update the configuration settings in the SendingContentModel event. I currently do a basic string search and replace for "guids/ids,"but this causes each node to take a couple minutes to load, which is obviously a poor experience.

    Any suggestions on how to improve performance? My current idea is to use a LazyCollection to gather mappers for each data type so that it can directly map the desired property instead of searching through a bunch of text (similar conceptually to the mappers in uSync). Below is my current code.

    private void EditorModelEventManager_SendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs<ContentItemDisplay> e) {
        var target = e.Model;
        int rootId;
        if (target.Id == 0) {
            if (target.ParentId <= 0) {
                return;
            }
            var node = _contentService.GetById(target.ParentId);
            rootId = int.Parse(node.Path.Split(',')[1]);
        }
        else {
            rootId = int.Parse(e.Model.Path.Split(',')[1]);
        }
        var mappings = _configMapperService.GetMappingsByRootId(rootId);
        foreach (var variant in e.Model.Variants) {
            foreach (var tab in variant.Tabs) {
                foreach (var property in tab.Properties) {
                    if (property.Value != null) {
                        var type = property.Value.GetType();
                        var stringValue = JsonConvert.SerializeObject(property.Value);
                        foreach (var mapping in mappings) {
                            stringValue = stringValue.Replace("\"" + mapping.Source.Id.ToString() + "\"", "\"" + mapping.Target.Id.ToString() + "\"");
                            stringValue = stringValue.Replace(":" + mapping.Source.Id.ToString(), ":" + mapping.Target.Id.ToString());
                            stringValue = stringValue.Replace("umb://document/" + mapping.Source.Key.ToString().Replace("-", ""), "umb://document/" + mapping.Target.Key.ToString().Replace("-", ""));
                        }
                        property.Value = JsonConvert.DeserializeObject(stringValue, type);
                    }
                    if (property.Config != null) {
                        var configString = JsonConvert.SerializeObject(property.Config);
                        foreach (var mapping in mappings) {
                            configString = configString.Replace("\"" + mapping.Source.Id.ToString() + "\"", "\"" + mapping.Target.Id.ToString() + "\"");
                            configString = configString.Replace(":" + mapping.Source.Id.ToString(), ":" + mapping.Target.Id.ToString());
                            configString = configString.Replace("umb://document/" + mapping.Source.Key.ToString().Replace("-", ""), "umb://document/" + mapping.Target.Key.ToString().Replace("-", ""));
                        }
                        property.Config = JsonConvert.DeserializeObject<Dictionary<string, object>>(configString);
                    }
                }
            }
        }
    }
    
  • Steve Megson 151 posts 1022 karma points MVP c-trib
    Oct 07, 2019 @ 20:47
    Steve Megson
    1

    The first thing that springs to mind is that property.Config will be the same every time you see the same data type, so you could keep a cache of the mapped versions for each rootId and property.DataTypeKey. Or perhaps use rootId and configString so that you don't have to deal with clearing the cache when data types are edited.

    However, I guess that only cuts the work in half and cuts the time from a couple of minutes to a minute, so we need to do more.

    Roughly how many nodes are you mapping, and how many matches do you expect in a typical document? A couple of easy improvements could be to use Key.ToString("N") rather than Key.ToString().Replace("-", ""), and to create a StringBuilder from stringValue. Then you can use StringBuilder.Replace, which can modify the current instance rather than allocating a new string for the result.

    If that doesn't help, I'd consider keeping the string-based search, but using compiled Regexes instead of many String.Replaces. The easiest to implement would probably be storing two Regexes with each mapping, something like [":](1234)"? and umb://document/(a6c82367f43c425db6f03e85a8fd98d9).

    A more complex option which may be more efficient would be two Regexes for the whole set of mappings, something like

     [":](1234|1235|1236|.1237|..)"?
    

    and the equivalent to match all the source keys. This adds the complexity of working out which ID/key should replace each match. I'd store a dictionary mapping from source ID/key to target ID/key, then use a MatchEvaluator to look up the replacements.

  • Jesse Andrews 191 posts 716 karma points c-trib
    Oct 07, 2019 @ 21:54
    Jesse Andrews
    0

    Thanks for the suggestions. These both sound useful, though I also suspect the regex solution will be more efficient. I didn't realize MatchEvaluator supported dynamically setting the replacement word.

    As for how many nodes are being mapped, it currently does 23 and I don't expect there to be many more nodes will need to be mapped. I expect there would be somewhere in the neighborhood of 20-30 matches per page.

    Will post an update once I've tested this change.

  • Jesse Andrews 191 posts 716 karma points c-trib
    Oct 10, 2019 @ 00:37
    Jesse Andrews
    0

    Updating the string replacement to use a single regex replacement helped significantly. I also added in some caching that I forgot to migrate over from a different branch. It now takes under 20ms once the content enters the cache.

    Thanks for the suggestions.

  • Marc Goodson 2141 posts 14324 karma points MVP 8x c-trib
    Oct 07, 2019 @ 20:53
    Marc Goodson
    1

    Hi Jesse

    Another thought could be to have the 'start node' for the data type expressed via XPath in a configuration option relative to the site, instead of the id of a specific node.

    If you have a look at the MultiNodeTreePicker, this has the option to set the start node to pick from via XPath

    enter image description here

    The Multinode tree picker works in a similar way to the content picker.

    in the example here the xpath is using $site, to find the 'homepage' node of the site the picker is being used on, and then the document type alias of the 'blog section' where blog posts are created...

    ... not the answer to your question, but thought I'd mention in case it helped think about the problem in a different way.

    regards

    Marc

Please Sign in or register to post replies

Write your reply to:

Draft