Addressing Archive Count Issues in v1.6 after Importing from BlogML Module

Topics: Customizing Orchard, Installing Orchard
Mar 12, 2013 at 1:48 PM
Scenario: Running Orchard 1.6 and migrating from another engine (SubText) using the BlogML format & the BlogML module.

The module was written for Orchard 1.4 and from a few I talked to that used it, it worked fine. However I'm assuming something changed in the blog logic from Orchard 1.4 > 1.6. Basically when a blog post is published, it fires a handler that uses the current timestamp as the publish date which also is used for updating the archive month counters. Immediately after this, the BlogML module changes the Publish date so it looks correct in the UI & DB, but the counters are already set.

Changeset b486d94960b6 that added a command to the Orchard.Blogs module, but I had a bunch of issues with it. Not entirely sure how to comment / update a changeset, so posting my findings & fix here. First, here are the issues I found and how to address them. Then I'm posting the code from my replaced ArchiveService.cs file:
  • In the original file, lines 25 & 31 were getting the oldest & news blog posts in the DB, but not filtering based on the actual blog specified (see my introduction of a WHERE clause on lines 26 & 32).
  • For some reason, I was getting a ton of errors on line 61 in the changeset… NHibernate/LINQ couldn’t evaluate the x.CreatedUtc.Value property. Thus, I changed it and created (my file, lines 57-66) a generic list that had all the dates of each blog post & updated that line in the where clause to look at my generic list rather than querying the DB for counts. For me, this will result in 1 big call to the DB and the creation of a 1,500 item list (# of blog posts) vs. 120 DB count calls (I’ve been blogging for 10 years). Since it’s only done one time updating the archive… I’m not concerned with perf.
  • Issue in calculating the FROM & TO date range (original changeset lines 59 & 60). You’ll see my change in lines 70-72. Basically adding a month to the UTC converted date does just that… adds a month… but it might not jive with the timezone. For instance I had posts being missed on 12/31/2003. The reason is that it would use the start range of timezone formatted (I’m GMT -0500) of 11/30/2003 19:00:00 (-5hrs from 11/31/2003 00:00:00). When the changeset figured the end range (line 60), it resulted in 12/30/2003 19:00:00… but it should have been the 31st. The FIX: do the calculation before converting from UTC (see my line 72).
  • Another issue was that I didn’t want leading/trailing zero months. For instance, I started blogging in September 2003. The changeset would have put zero counters for January – August 2003. Same is true on the back side (I’m importing to March 2013, but I don’t want zero’d counters from April – December 2013). I addressed this in lines 74-81.
  • Finally, the way values were added to the DB caused some off by one dates. For instance using the year & month from the TO & FROM variables meant that it was off due to the timezone change. I fixed this by using the indexes of the for loops.
My ArchiveService.cs fixed file (this should overwrite the file in the changeset:
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Blogs.Models;
using Orchard.ContentManagement;
using Orchard.Core.Common.Models;
using Orchard.Data;

namespace Orchard.Blogs.Services {
  public class ArchiveService : IArchiveService {
    private readonly IRepository<BlogPartArchiveRecord> _blogArchiveRepository;
    private readonly IContentManager _contentManager;
    private readonly IWorkContextAccessor _workContextAccessor;

    public ArchiveService(
        IRepository<BlogPartArchiveRecord> blogArchiveRepository,
        IContentManager contentManager,
        IWorkContextAccessor workContextAccessor) {
      _blogArchiveRepository = blogArchiveRepository;
      _contentManager = contentManager;
      _workContextAccessor = workContextAccessor;

    public void RebuildArchive(BlogPart blogPart) {

      var first = _contentManager.Query<BlogPostPart>().Where<CommonPartRecord>(bp => bp.Container.Id == blogPart.Record.Id).OrderBy<CommonPartRecord>(x => x.CreatedUtc).Slice(0, 1).FirstOrDefault();

      if (first == null) {

      var last = _contentManager.Query<BlogPostPart>().Where<CommonPartRecord>(bp => bp.Container.Id == blogPart.Record.Id).OrderByDescending<CommonPartRecord>(x => x.CreatedUtc).Slice(0, 1).FirstOrDefault();

      DateTime? start = DateTime.MaxValue;
      if (first.As<CommonPart>() != null) {
        start = first.As<CommonPart>().CreatedUtc;

      DateTime? end = DateTime.MinValue;
      if (last.As<CommonPart>() != null) {
        end = last.As<CommonPart>().CreatedUtc;

      // delete previous archive records
      foreach (var record in _blogArchiveRepository.Table.Where(x => x.BlogPart == blogPart.Record)) {

      if (!start.HasValue || !end.HasValue) {

      // get the time zone for the current request
      var timeZone = _workContextAccessor.GetContext().CurrentTimeZone;

      // build a collection of all the post dates
      List<DateTime> blogPostDates = new List<DateTime>();
      var blogPosts = _contentManager.Query<BlogPostPart>().Where<CommonPartRecord>(bp => bp.Container.Id == blogPart.Record.Id);
      foreach (var blogPost in blogPosts.List()) {
        if (blogPost.As<CommonPart>() != null)
          if (blogPost.As<CommonPart>().CreatedUtc.HasValue) {
            DateTime timeZoneAdjustedCreatedDate = TimeZoneInfo.ConvertTimeFromUtc(blogPost.As<CommonPart>().CreatedUtc.Value, timeZone);

      for (int year = start.Value.Year; year <= end.Value.Year; year++) {
        for (int month = 1; month <= 12; month++) {
          DateTime fromDateUtc = new DateTime(year, month, 1);
          var from = TimeZoneInfo.ConvertTimeFromUtc(fromDateUtc, timeZone);
          var to = TimeZoneInfo.ConvertTimeFromUtc(fromDateUtc.AddMonths(1), timeZone);

          // skip the first months of the first year until a month has posts
          //  for instance, if started posting in May 2000, don't write counts for Jan 200 > April 2000... start May 2000
          if (from < TimeZoneInfo.ConvertTimeFromUtc(new DateTime(start.Value.Year, start.Value.Month, 1), timeZone))
          // skip the last months of the last year if no posts
          //  for instance, no need to have archives for months in the future
          if (to > end.Value.AddMonths(1))

          //var count = _contentManager.Query<BlogPostPart>().Where<CommonPartRecord>(x => x.CreatedUtc.Value >= from && x.CreatedUtc.Value < to).Count();
          var count = blogPostDates.Where(bp => bp >= from && bp < to).Count();

          var newArchiveRecord = new BlogPartArchiveRecord { BlogPart = blogPart.Record, Year = year, Month = month, PostCount = count };
Mar 12, 2013 at 11:11 PM
Hi, do you want to make a pull request with that?
Mar 13, 2013 at 1:49 AM
Once I figure out what that means and how to do it, then yes I will submit the change. :)
Mar 13, 2013 at 2:04 AM
OK, do you need help with that?
Mar 13, 2013 at 10:15 AM
Maybe more of a pointer... prefer to learn it. Plus, very eager with Orchard and have a few customizations I'd like to create (modules) so need to figure it out. Haven't used CodePlex or collaborated in an open source project before.
Mar 13, 2013 at 11:05 AM
Before you can make a pull request, you need to create a fork. You can see a fork as your private copy of the Orchard source repository. You clone that fork to your harddrive, make changes to the source, and commit & push them back so that your changes are stored on CodePlex in your private fork.

When you are done with your work, you can create a pull request for your private fork, so that we can review what you did, and merge your changes into the Orchard source repository.

CodePlex has some nice instructions how to clone and work with TortoiseHg, which is a client application to work with the Mercurial source control system.
Mar 13, 2013 at 11:07 AM
Thanks sfmskywalker... that helps. I'll give it a shot.
Mar 19, 2013 at 1:56 PM
OK, I forked it... I'll make a few tweaks and let you know when it's done...
Mar 19, 2013 at 5:13 PM
Awesome, thanks.
Mar 19, 2013 at 9:41 PM
Done... changes applied and pull request submitted. This was my first one, so if I didn't do something correct, please let me know. I followed the instructions on the TotoiseHg walkthrough on Codeplex.

My fork: andrewconnell/ImportUtilityHelperCommands
Revision 6871