Inheriting data from ancestor documents in RavenDB

167 views Asked by At

I'm using RavenDB to store three types of curriculum: Units, Lessons, and Activities. These three types all inherit from CurriculumBase:

public abstract class CurriculumBase
{
    public string Id { get; set; }
    public string Title { get; set; }
    public List<string> SubjectAreaIds { get; set; }
    // non-relevant properties removed
}

These documents have a hierarchical relationship, so I've modeled the hierarchy as a separate single document as recommended here: Modelling Hierarchical Data with RavenDB

public class CurriculumHierarchy
{
    public class Node
    {
        public string CurriculumId { get; set; }
        public string Title { get; set; }
        public List<Node> Children { get; set; }

        public Node()
        {
            Children = new List<Node>();
        }
    }

    public List<Node> RootCurriculum { get; set; }

    public CurriculumHierarchy()
    {
        RootCurriculum = new List<Node>();
    }
}

I need to be able to do searches across all curriculum documents. For simple properties, that seems easy enough to do with a multi-map index.

However one of the properties I need to be able to search by (in combination with the other search criteria) is SubjectAreaId. I need to be able to get curriculum for which it or any of its ancestors have the specified subject area id(s). In other words, for search purposes, documents should inherit the subjectAreaIds of their ancestors.

I've considered de-normalizing subjectAreaIds, and storing the full calculated set of subjectAreaIds in each document, but that will require updates whenever the hierarchy itself or the subjectAreaIds of any of a given document's ancestors change. I'm hoping this is something I can accomplish with an index, or perhaps an entirely different approach is needed.

2

There are 2 answers

1
Ayende Rahien On

You can use LoadDocument to load the parents during indexing.

http://ravendb.net/docs/article-page/3.0/csharp/indexes/indexing-related-documents

0
Kevin Krueger On

The main challenge I encountered was that I had written code in CurriculumHierarchy to get a document's ancestors, but this code isn't executable during indexing.

To solve this, I added a read-only property to CurriculumHierarchy which generates a dictionary of ancestors for each document:

public Dictionary<string, IEnumerable<string>> AncestorLookup
{
    get
    {
        // Not shown: build a dictionary where the key is an  
        // ID and the value is a list of the IDs for 
        // that item's ancestors
    }
}

This dictionary is serialized by Raven and therefore available for indexing.

Then my index ended up looking like this:

public class Curriculum_Search : AbstractMultiMapIndexCreationTask
{
    public Curriculum_Search()
    {
        AddMap<Activity>(
            activities =>
                from activity in activities
                let hierarchy = 
                    LoadDocument<CurriculumHierarchy>("curriculum_hierarchy")
                let ancestors = 
                    LoadDocument<CurriculumBase>(hierarchy.AncestorLookup[activity.Id])
                select new
                {
                    subjectAreaIds = ancestors.SelectMany(x => x.SubjectAreaIds).Distinct().Union(activity.SubjectAreaIds),
                });

       // Not shown: Similar AddMap statements for Lessons and Units
    }
}

I was a bit concerned about performance, but since there are less than 2000 total curriculum documents, this seems to perform acceptably.