Update to Vandelay.Industries.TagCloudService to support Localization

Topics: Localization, Writing modules
Jan 3, 2013 at 12:19 PM

Hi,

I'm not (yet) a full grown guru in the Orchard CMS API, so I would like some feedback on the stuff I'm doing here.  I started using the Losjland localization module that adds a tag cloud filter, but this seems to do funny things for me. So I decided to simply start from the Vandelay Industries module and change the stuff needed until it works so I can move it to a separate module later. 

In contradiction to the Losjland module, I wanted to make sure everything regarding the localization was passed in one go into the query so this can be cached in one go as well. I do believe this is not yet the case as I do a Where() after List(), which I think simply filters the already queried stuff from the database. Doing the Where() before the List on the Join<LocalizationPart>() deal, only provides me with a CultureId member which only contains 1 or 2 (I have 2 cultures defined in my site), so I don't really know what to do with that. 

The below code works fine for me and seems to be quite performant, although I'd rather have it filter at the database level. Can any of you have a look and tell me what I'm doing right/wrong here? Thanks in advance:

public IEnumerable<TagCount> GetPopularTags(int buckets, string slug)
        {
            // Code Prerequisite: Localization Part on Page, Blog Post and any other where tags should be queried
            var currentCulture = _cultureManager.GetCurrentCulture(_workContextAccessor.GetContext().HttpContext);
            // Add Culture to the cache key, otherwise the same set of tags is returned for all requested cultures
            var cacheKey = "Vandelay.TagCloud." + (slug ?? "") + '.' + currentCulture + '.' + buckets;
            return _cacheManager.Get(cacheKey,
                              ctx =>
                              {
                                  ctx.Monitor(_signals.When(VandelayTagcloudTagschanged));
                                  IEnumerable<TagCount> tagCounts;
                                  if (string.IsNullOrWhiteSpace(slug))
                                  {
                                      tagCounts = _contentManager
                                          .Query<TagsPart, TagsPartRecord>(VersionOptions.Published)
                                          .Join<CommonPartRecord>()
                                          .Join<LocalizationPartRecord>()
                                          .List()
                                          // Is this correct or should this be a query filter before .List() 
                                          // using l.CultureId and if so, how?
                                          .Where(l => {
                                              var lp = l.ContentItem.Parts.OfType<LocalizationPart>().SingleOrDefault();
                                              return lp != null && lp.Culture != null && lp.Culture.Culture == currentCulture;
                                          })
                                          .SelectMany(t => t.CurrentTags)
                                          .GroupBy(t => t.TagName)
                                          .Select(g => new TagCount
                                          {
                                              TagName = g.Key,
                                              Count = g.Count()
                                          })
                                          .ToList();
                                  }
                                  else
                                  {
                                      var container = _contentManager
                                          .Query<AutoroutePart, AutoroutePartRecord>()
                                          .ForVersion(VersionOptions.Published)
                                          .Where(c => c.DisplayAlias == slug)
                                          .List()
                                          .FirstOrDefault();
                                      if (container == default(AutoroutePart)) return new List<TagCount>();
                                      tagCounts = _contentManager
                                          .Query<TagsPart, TagsPartRecord>(VersionOptions.Published)
                                          .Join<CommonPartRecord>()
                                          .Where(t => t.Container.Id == container.Id)
                                          .Join<LocalizationPartRecord>()
                                          .List()
                                          .Where(l => {
                                              var lp = l.ContentItem.Parts.OfType<LocalizationPart>().SingleOrDefault();
                                              return lp != null && lp.Culture != null && lp.Culture.Culture == currentCulture;
                                          })
                                          .SelectMany(t => t.CurrentTags)
                                          .GroupBy(t => t.TagName)
                                          .Select(g => new TagCount
                                          {
                                              TagName = g.Key,
                                              Count = g.Count()
                                          })
                                          .ToList();
                                  }
                                  var maxCount = tagCounts.Max(tc => tc.Count);
                                  var minCount = tagCounts.Min(tc => tc.Count);
                                  var delta = maxCount - minCount;
                                  if (delta != 0)
                                  {
                                      // Linear fitting algorithm to associate tags to buckets
                                      // There are smarter ones, left as an exercise to the reader.
                                      // (see for example http://www.codeproject.com/KB/recipes/K-Mean_Clustering.aspx)
                                      foreach (var tagCount in tagCounts)
                                      {
                                          tagCount.Bucket = (tagCount.Count - minCount) * (buckets - 1) / delta + 1;
                                      }
                                  }
                                  return tagCounts;
                              });
        }

Jan 15, 2013 at 7:48 AM

Feedback anyone?

Coordinator
Jan 15, 2013 at 5:48 PM

Not sure what you're asking. If in doubt about perf, profile it. If it works and performs fine, leave it alone.

Jan 15, 2013 at 6:46 PM

Nothing wrong with performance, but it doesn't work correctly with multiple cultures, hence the update. As stated earlier, I first tried to use a third-party module to filter the tag cloud's tags on the current user's site culture, but this didn't quite behave as expected. That's why I made the above changes which do seem to work for me. What I'm asking for is some feedback on whether the above code is performant enough seeing that I'm issuing a.List(), which to my belief queries everything from the database, after which I use a Linq .Where() to filter out the results. I was thinking to get to a solution where I could do the .Where() before the .List(), thus filtering inside the database call...

Coordinator
Jan 15, 2013 at 8:49 PM

Well, usually perf is measured, not guessed. I'm not sure why the Where can't be put before the List. A workaround could be to switch to Hql if that turns out to be a problem.

Jan 16, 2013 at 1:37 PM

You're absolutely right, I am however working from a general assumption that database filters are faster than querying everything and then filtering it out in code.

Issue was not that the .Where couldn't be put before the .List, I just didn't know what to do with the ID. I've managed to solve it, however.

Thanks for your effort.