Neo4JClient – Getting Path Results

So, let’s get this straight off the bat – Craig Brett has written about using the PathResults from Neo4jclient (a .NET REST client to Neo4J), but times have moved on, and there are some specifics I’ve needed to work out with the newer clients (this post – for reference uses Neo4jclient version: 1.0.0.590).

The problem

I want to get all the nodes and relationships from starting node ‘n’ going out ‘x’ levels. In Cypher – that’s pretty simple:

START n = node(2)
MATCH p = n-[:RELATION*1..3]-()
RETURN p;

and translating it into Neo4jclient is simple as:

client.Cypher
      .Start(new { n = new NodeReference(2) })
      .Match("p = n-[:RELATION*1..3]-()")
      .Return<PathsResult>("p");

So far so easy… The problem as Craig discovered is that PathsResult is a collection of REST URLs – which is what (by default) Neo4j gives you. What we want is the actual Nodes and RelationshipInstances, the dumb way to achieve these goals (which to my shame I have suggested in the past) is to loop through the Relationships and pull out the relationships from Neo4j, then do some parsing.. Obviously this is horrendous, you end up making 1 DB call to get the paths, then n subsequent ones depending on how many paths you bring back, either way – not pretty!

The Solution

Craig uses the ‘Extract’ function, after getting a suggestion from Peter Neubauer on StackOverflow, and mentions that he uses in the format:

EXTRACT(n in NODES(p) : n) as Nodes

The important part of this call isn’t the EXTRACT bit, but the ‘NODES’ bit. NODES returns all the nodes on a path, the above code can in fact be replaced with just ‘NODES(p)’ and it would work exactly the same way. As Craig says, the leap to get the relationships is a short one, we just use ‘RELATIONSHIPS’ (or ‘rels’).

But how do we get this out using Neo4jclient? Well – we need to do some projections in our return. First, we’ll probably want a class to contain all our new stuff into:

public class PathsResult<TNode, TRelationship> where TRelationship : Relationship, new()
{
    public IEnumerable<Node<TNode>> Nodes { get; set; }
    public IEnumerable<RelationshipInstance<TRelationship>> Relationships { get; set; }
}

And, lets write a method to cope with this:

public static class Neo4JClientExtensions
{
    public static ICollection<PathsResult<TNode, TRelationship>> Paths<TNode, TRelationship>(this IGraphClient client, NodeReference<TNode> rootNode, int levels = 1) 
        where TRelationship : Relationship, new()
    {
        var pathsQuery = client.Cypher
            .Start(new { n = rootNode })
            .Match(string.Format("p=n-[:{0}*1..{1}]->()", new TRelationship().RelationshipTypeKey, levels))
            .Return(p => new PathsResult<TNode, TRelationship>
            {
                Nodes = Return.As<IEnumerable<Node<TNode>>>("nodes(p)"),
                Relationships = Return.As<IEnumerable<RelationshipInstance<TRelationship>>>("rels(p)")
            });

        return pathsQuery.Results.ToList();
    }
}

OK, I’ve put it into a static class so I can use it as an extension method, but aside from that it should be pretty easy to follow. The ‘Start’ and ‘Match’ clauses are pretty much the same (aside from some niceties to paramaterize the arguments), the ‘Return’ clause is the main difference. Here we project the results to the correct types.

We need to use ‘Return.As<>’ (not p.As<>) to be able to pass in some custom Cypher to bring out the Nodes / Relationships, and well… That’s about it, from 1+n calls to the DB to just 1.

Source Code

You can get the source code here: https://bitbucket.org/cskardon/neo4jclient.pathsexample

Any problems, let me know!

Some Heads Up

I’m a big fan of the ICollection<> interface, I generally use it all over the place instead of IEnumerable – ignoring that this is probably bad practice (but as I 9 times out of 10 end up calling .ToList() it usually works out OK), except in this case. My initial ‘PathsResult’ class used ICollections – not IEnumerables. This causes neo4jclient to crash (wrongly trying to say it was to blame).

Print | posted @ Tuesday, July 23, 2013 3:27 AM

Comments on this entry:

Gravatar # re: Neo4JClient – Getting Path Results
by Craig Brett at 1/14/2014 12:06 PM

Thanks for writing this entry, I hadn't figured out how paths would work with the new syntax. Nice work!
Gravatar # re: Neo4JClient – Getting Path Results
by Chris at 1/14/2014 12:10 PM

Heh, Just building on top of your work :)
Post A Comment
Title:
Name:
Email:
Comment:
Verification: