Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RavenDB and hierarchical documents

I am having difficulties solving this problem using RavenDB. I have these classes. I excluded a lot of properties from them to keep the example simple.

public class Menu
{
    public string Name { get; set; }
    public List<NavigationNode> Nodes { get; set; }
}

public class NavigationNode
{
    public string Text { get; set; }
    public Guid? PageId { get; set; }
    public string NodeType { get; set; }
    public List<NavigationNode> Nodes { get; set; }
}

public class Page
{
    public Guid PageId { get; set; }
    public string Slug { get; set; }
}

So as you can see this is about rendering a navigation menu. Node lists are hierarchical and can go deep, in theory infinitely(of course in reality 2-4 sub levels only). At first i stored the Slug in the Node but realised that, what happens if the page Slug changes, changing all nodes when a page changes slug i would have to loop through all menus, climb down the hierarchy and find all those Slug values to change them, which does not sound like an optimal solution.

So i was thinking it should be possible to build an index that combines the Page Slug with rest of the data from the Node in a hierarchical structure.

I have been reading about Map Reduce, Multimap and recurse but i am not even sure where to start.

I found this http://ravendb.net/docs/2.0/client-api/querying/static-indexes/indexing-hierarchies

Here is a simple example i tried just to get something started, and i cannot even get that to work since i really don't understand the example on the page i linked to above.

  public class NavigationIndex : AbstractIndexCreationTask<Menu>
   {
        public NavigationIndex()
        {
            Map = menus => from menu in menus
                           from node in Recurse(menu, x => x.Nodes)
                           select new
                                      {
                                          WhatIsThis = node // <- Why is this a collection?
                                      };
        }
    }

According to the example node should not be a collection but an actual NavigationNode object.

Is it possible to achieve what i want in RavenDB and what am i doing wrong in the example?

Please feel free to ask anything you find confusing.

I apologize for the confusion. I'll try to explain it.

EDIT:

Changing PageId to string will not be a problem. I am using Guids since i need to be able to generate primary key ID:s before inserting. Anyway, what i want to Query for from the index is a hiearachical tree of navigation links where the Pages Slug is included. So i can recursively loop out a navigation menu on the website.

Answers to Matt Johnson's questions:

  1. I want a output of a class you can see below
  2. Page is a separate document
  3. I am only going to query by Menu Name

    public class NavigationIndexItem{ public string MenuName { get; set; } public string Text { get; set; } public string Slug { get; set; } public string NodeType { get; set; } public List ChildItems { get; set; } }

Now when i see the above class i think i might a bit on the wrong path.

But anyway i will make some minor changes and thank you Matt for answering. However i am still running into same problem as before.

This row in your example: where node.PageId != null

node is not an instance of a specific NavigationNode but yet another collection so i cannot check the PageId property on it. I only get a list of LINQ extensions.

like image 391
Kimpo Avatar asked Jan 30 '13 14:01

Kimpo


1 Answers

I'm making some assumptions of what you want. See my comments on the original question. But I think this is what you are after.

First - you need to change the id of the Page class to Id instead of PageId. This is so Raven will use your guid as part of its document id. Really you would be better off with string Ids, but this will still work.

Then you can do the following:

public class NavigationIndex : AbstractIndexCreationTask<Menu>
{
    public NavigationIndex()
    {
        Map = menus => from menu in menus
                       from node in Recurse(menu, x => x.Nodes)
                       where node.PageId != null
                       let page = LoadDocument<Page>("pages/" + node.PageId)
                       select new
                       {
                           menu.Name,
                           node.Text,
                           node.PageId,
                           page.Slug
                       };
    }
}

This uses the new LoadDocument feature of RavenDB 2.0, which is much more suitable than multi-map for your scenario.

like image 92
Matt Johnson-Pint Avatar answered Oct 03 '22 18:10

Matt Johnson-Pint