Search code examples
relationshipcypherneo4jclient

Neo4jClient - Retrieving relationship from Cypher query


I'm having trouble retrieving matched relationships from a Cypher query.

I have this simple trial code:

var movie = client.Create(new Movie { Title = "The Matrix" });

client.Create(new Actor { Name = "Keanu Reeves" },
    new ActedIn(movie, new ActedInPayload { Role = "Neo" }));

client.Create(new Actor { Name = "Hugo Weaving" },
    new ActedIn(movie, new ActedInPayload { Role = "Agent Smith" }));

var actorsAndRoles = client
    .Cypher
    .Start(new { movie = movie })
    .Match("actor-[r:ACTED_IN]->movie")
    .Return((actor, r) => new
    {
        Actor = actor.As<Node<Actor>>()
        // ActedIn = r.As<?????>()
    })
    .Results;

Problem is that I can't work out how to cast the r (with the matched relationship).

Tried various ".As" type casts, but none work. Casting to Relationship doesn't work because my relationship class doesn't have a parameterless constructor - but then the Relationship base class doesn't have a parameterless constructor itself, so don't think that would work. Casting to RelationshipReference on the other hand causes an exception. Not casting at all (just returning r) causes a "not supported" exception.

There are some related SO entries about this issue, but the suggested code there either no longer works or is deprecated.

How do I retrieve the matched relationship?


Solution

  • You can create a parameterless constructor for your relationship type, you just pass it 'duff' data, which I know sounds bad, but as you don't really need it, it won't hurt you:

    public class ActedIn : Relationship<ActedInPayload>, IRelationshipAllowingSourceNode<Actor>, IRelationshipAllowingTargetNode<Movie>
    {
        public ActedIn() : base(-1, null) {}
        public ActedIn(NodeReference targetNode, ActedInPayload data) : base(targetNode, data) {}
    
        public override string RelationshipTypeKey
        {
            get { return "ACTED_IN"; }
        }
    }
    

    So this is the ActedIn class, with the parameterless constructor chaining a '-1' down to the base constructor.

    Your query then becomes:

    var actorsAndRoles = client
        .Cypher
        .Start(new {movie})
        .Match("actor-[r:ACTED_IN]->movie")
        .Return((actor, r) => new
            {
                Actor = actor.As<Node<Actor>>(),
                ActedIn = r.As<RelationshipInstance<ActedInPayload>>()
            })
        .Results;
    

    Note, it's being cast to a RelationshipInstance of type ActedInPayload not ActedIn, then, to get the data you might want:

    foreach (var actorsAndRole in actorsAndRoles)
    {
        Console.WriteLine("{0} acted as {1} - which is in the db as {2}-[ACTED_IN]->{3}", 
            actorsAndRole.Actor.Data.Name, 
            actorsAndRole.ActedIn.Data.Role, 
            actorsAndRole.ActedIn.StartNodeReference.Id, 
            actorsAndRole.ActedIn.EndNodeReference.Id);
    }
    

    Which will give you something like:

    Keanu Reeves acted as Neo - which is in the db as 482-[ACTED_IN]->481

    Hugo Weaving acted as Agent Smith - which is in the db as 483-[ACTED_IN]->481

    Though obviously with different numbers on your own DB.