Writing an Archive Calendar, part 2

Next up in this series on writing the calendar of archived posts, is the PostsReader class. This class queries the database for the posts for a particular month or a particular day. It utilizes a couple of other helper classes that manage the caching of results, so we'll look at these first.

The first helper class is the PostCollectionSet. This is a set of already-computed collections of posts and is the granularity with which post collections are stored in the application cache. The set is declared as generic dictionary.

  public class PostCollectionSet : Dictionary<PostCollectionKey, PostCollection> {
    static string CacheKey = "jmbPostCollectionSet";

    public static PostCollectionSet GetInstance() {
      PostCollectionSet set = null;
      HttpContext context = HttpContext.Current;
      if (context == null)
        set = new PostCollectionSet();
      else {
        set = (PostCollectionSet)context.Cache[CacheKey];
        if (set == null) {
          set = new PostCollectionSet();
          context.Cache.Add(CacheKey, set, null, DateTime.Now.AddMinutes(10.0), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
        }
      }
      return set;
    }
  }

In essence, the GetInstance() method, queries the current HttpContext for the cache dictionary, and adds a new set if there isn't one present yet. The expiration is set to 10 minutes.

The PostCollectionKey class used as the key type in this dictionary is simple too: it's merely a class that stores the date of a post collection, with a special GetHashCode() method so it acts as a good key for this particular dictionary.

  public class PostCollectionKey {
    private int year;
    private int month;
    private int day;
    public PostCollectionKey(int year, int month, int day) {
      this.year = year;
      this.month = month;
      this.day = day;
    }
    public override int GetHashCode() {
      return (year * 1000) + (month * 100) + day;
    }
  }

Now we can look at the PostsReader class.

  public class PostsReader {

    private static Query BuildRangePostsQuery(DateTime start, DateTime end) {
      Query q = Post.CreateQuery();
      Column published = new Column("Published", DbType.DateTime, typeof(DateTime), "Published", false, false);
      Column isPublished = new Column("IsPublished", DbType.Boolean, typeof(bool), "IsPublished", false, false);
      Column isDeleted = new Column("IsDeleted", DbType.Boolean, typeof(bool), "IsDeleted", false, false);
      Column categoryID = new Column("CategoryID", DbType.Int32, typeof(Int32), "CategoryID", false, false);
      // this ordering is required by PostCount()
      q.OrderByDesc(published);
      q.AndWhere(published, start, Comparison.GreaterOrEquals);
      q.AndWhere(published, end, Comparison.LessThan);
      q.AndWhere(isPublished, true, Comparison.Equals);
      q.AndWhere(isDeleted, false, Comparison.Equals);
      q.AndWhere(categoryID, 1, Comparison.NotEquals); // uncategorized
      return q;
    }

    private static Query BuildMonthPostsQuery(int year, int month) {
      DateTime startOfMonth = new DateTime(year, month, 1);
      DateTime startofNextMonth = startOfMonth.AddMonths(1);
      return BuildRangePostsQuery(startOfMonth, startofNextMonth);
    }

    private static Query BuildDayPostsQuery(int year, int month, int day) {
      DateTime startOfDay = new DateTime(year, month, day);
      DateTime startofNextDay = startOfDay.AddDays(1);
      return BuildRangePostsQuery(startOfDay, startofNextDay);
    }

    public static PostCollection GetPostsForMonth(int year, int month) {
      return GetPostsForDate(year, month, 0);
    }

    public static PostCollection GetPostsForDate(int year, int month, int day) {
      PostCollectionSet set = PostCollectionSet.GetInstance();
      PostCollectionKey key = new PostCollectionKey(year, month, day);
      PostCollection posts = null;
      if (set.ContainsKey(key)) {
        posts = set[key];
      }
      else {
        if (day == 0)
          posts = PostCollection.FetchByQuery(BuildMonthPostsQuery(year, month));
        else
          posts = PostCollection.FetchByQuery(BuildDayPostsQuery(year, month, day));
        set[key] = posts;
      }
      return posts;
    }
  }

The interesting (and boring at the same time) method here is the BuildRangePostsQuery() method. It takes two dates (a start and end date), and constructs a query that can be used to fetch all of the published posts in that date range, ordered in descending order of published date/time, and not deleted or belonging to category 1 (the uncategorized category).

After that one, the next one is the workhorse GetPostsForDate() method. This works out if the requested post collection is in the cache, and if not, builds the query, executes it, and both stores the result in the cache and returns it to the caller.

(Part 1 is here, part 2 here, part 3 here, part 4 here, part 4a here, part 4b here.)

Album cover for Greatest Hits Now playing:
Mott the Hoople - All the Way from Memphis
(from Greatest Hits)


Loading similar posts...   Loading links to posts on similar topics...

2 Responses

#1 Dew Drop - January 14, 2009 | Alvin Ashcraft's Morning Dew said...
14-Jan-09 12:36 PM

Pingback from Dew Drop - January 14, 2009 | Alvin Ashcraft's Morning Dew

#2 Writing an Archive Calendar, Part 2 said...
18-Jan-09 1:57 PM

Thank you for submitting this cool story - Trackback from DotNetShoutout

Leave a response

Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response