NHibernate Simple Patterns: Self-Referencing Nested Comments

Consider the need to display a group of comments the way Disqus displays them. We’d have to have a Comment class that looks kind of like this:

public class Comment
{
    public virtual int Id { get; set; }        
    public virtual string Body { get; set; }
    public virtual Post ParentPost { get; set; }
    public virtual Comment ParentComment { get; set; }
    public virtual IList<Comment> ChildComments { get; set; }
    public virtual DateTime DateCreated { get; set; }
    public virtual User Author { get; set; }
}

Mapped with this Classmap

public class CommentClassMap : ClassMap<Comment>
{
    public CommentClassMap()            
    {
        Table("Comments");
        Id(x => x.Id).GeneratedBy.Native();
        Map(x => x.Body);            
        Map(x => x.DateCreated);
        References(x => x.ParentComment)
            .LazyLoad()
            .Column("Comment_id");
        HasMany<Comment>(x => x.ChildComments)
            .LazyLoad()
            .KeyColumn("Comment_id")
            .Cascade.None();
        References(x => x.ParentPost)
            .LazyLoad();
        References(x => x.Author);          
    }
}

Now the main purpose is, I’d need to get all the Comments and their respective sub-comments for a specific ‘Post’. Ayende, already describes an easy solution for this problem in this article. Here’s the code we can use to load the tree:

IList<Comment> comments = _session.CreateQuery(@"select c from Comment c " +
    "left join fetch c.ChildComments cc " +                
    "where c.ParentPost.Id = :Id")
    .SetParameter("Id", id)
    .SetResultTransformer(new DistinctRootEntityResultTransformer())                
    .List<Comment>();

But this leaves one little problem: you’ll also be loading ChildComments that also have ChildComments at the same level as the Comments who have no ParentComment (Root Level Comments, 1st level Ancestors in the tree).

We only want to display the root comments on which all other Child/Descendants from the Tree are effectively based upon. So we’d need to manipulate the comments object for a little bit:

List<Comment> commentsForDisplay = new List<Comment>();

foreach (Comment comment in comments)
{
    if (comment.ParentComment == null)
    {
        commentsForDisplay.Add(comment);
    }
}

When you return the commentsForDisplay object, it loads the entire Comment Tree graph effectively, with all the ChildComments and their Descendants only being displayed with the proper Root Ancestor.

How do we display this in ASP.NET MVC with the Razor View Engine?

Consider using an inline Helper class in the view, or create a PartialView that accepts a list of comments as its Model:

@helper ShowCommentTree(IEnumerable<CommentViewModel> comments)
 {
     foreach (var item in comments)
     {
         @Html.Markdown(item.Body)
         @Html.ActionLink("Reply", "ReplyToComment", new { @id = item.Id })       

         if (item.ChildComments.Any())
         {
             @ShowCommentTree(item.ChildComments)
         }
     }
 }

Just fix the margins and styles to create the nested effect. This simply renders all your comments recursively until everything is displayed.