RavenDBSplitting entities across several documents

time to read 4 min | 684 words

There are occasions where it isn’t feasible or desirable to store our entity as a single document in RavenDB. A question that just came up was how to design votes for an entity using RavenDB.

The scenario is simple, we have our entity, Question (think stack overflow), which can have Up/Down votes. It would be very easy to design the system using a single document for the entity, like so:

{ //document id: questions/123
   Title: "How to handle Up/Down votes with Raven?",
   Content: "...",
   Votes: [
         { Up: true, User: "users/ayende" },
         { Up: false, User: "users/oren" },
  ]
}

As usual, the problem begins when you start to consider what happens when you want to deal with questions that may have large number of votes, or the common scenario where you just want to display the vote totals, and not pull the entire document to get that.

One option is to split things up. I guess you figured that out from the title of this blog post. The idea is to change the document structure to be:

{ //document id: questions/123
   Title: "How to handle Up/Down votes with Raven?",
   Content: "...",
}

{ //document id: questions/123/votes
   Votes: [
         { Up: true, User: "users/ayende" },
         { Up: false, User: "users/oren" },
  ]
}

Note that we have two separate documents here. Now we can load just the questions, or the questions and the votes. We still have a problem with getting the totals without loading potentially thousands of votes. It is pretty easy to solve this, however, using the following index:

from voteDoc in docs.VoteDocs
from vote in voteDoc.Votes
group vote by vote.Up into g
select new { Up = g.Key, Count = g.Count() }

Now we can query the index directly, to get the aggregated results:

session.LuceneQuery<VoteTotals>("Questions/VoteTotals")
            .SelectFields("__document_id", "Up", "Count")
            .ToList();

And if we want to get the votes themselves, they are easily available as well.

More posts in "RavenDB" series:

  1. (13 Oct 2017) Interlocked distributed operations
  2. (12 Oct 2017) Node.JS client is now in beta
  3. (11 Sep 2017) Support options
  4. (14 Aug 2017) Maintaining transaction boundary integrity in a distributed cluster
  5. (03 Aug 2017) Raven Query Language
  6. (13 Jul 2017) The admin’s backdoor is piping hot
  7. (10 Jul 2017) Securing the keys to the kingdom
  8. (04 Jul 2017) Unbounded results sets
  9. (13 Jun 2017) The etag simplification
  10. (12 Jun 2017) Data subscriptions, Part II
  11. (09 Jun 2017) Data subscriptions, Part I
  12. (19 May 2017) Managing encrypted databases
  13. (11 May 2017) Working with attachments
  14. (10 May 2017) Attachments
  15. (08 May 2017) Full database encryption