Search code examples
neo4jneo4jclient

Returning multi value in dynamic query using neo4j client


Following the question I asked: Build a dynamic query using neo4j client

I got an answer about how can I return value dynamically using string only.

When I'm trying to use the syntax to return multi values from the query it failed,
I tried the following query:

var resQuery2 = WebApiConfig.GraphClient.Cypher
            .Match("(movie:Movie {title:{title}})")
            .OptionalMatch("(movie)<-[r]-(person:Person)")
            .WithParam("title", title)
            .Return(() => Return.As<string>("movie, collect([person.name, head(split(lower(type(r)), '_')), r.roles])"));

I'm getting the following error:

The deserializer is running in single column mode, but the response included multiple columns which indicates a projection instead. If using the fluent Cypher interface, use the overload of Return that takes a lambda or object instead of single string. (The overload with a single string is for an identity, not raw query text: we can't map the columns back out if you just supply raw query text.)

Is it possible to return multiple nodes using only strings?


Solution

  • We can't get an output like in the question you asked previously - this is due to the fact that you are asking for a Node (the movie) and a Collection of strings (the collect) and they have no common properties, or even styles of property.

    Firstly, let's look at the painful way to do this:

    var q = gc.Cypher
        .Match("(movie:Movie)")
        .OptionalMatch("(movie)<-[r]-(person:Person)")
        .Return(() => Return.As<string>("{movie:movie, roles:collect([person.name, head(split(lower(type(r)), '_')), r.roles])}"));
    
    var results = q.Results;
    

    Here we take the query items (movie, r, person) and create a type with them the {} around the results, and cast that to a string.

    This will give you a horrible string with the Node data around the movie and then a collection of the roles:

    foreach (var m in results)
    {
        //This is going to be painful to navigate/use
        dynamic d = JsonConvert.DeserializeObject<dynamic>(m);
        Console.WriteLine(d.movie);
        Console.WriteLine(d.roles);
    }
    

    You'd be a lot better off doing something like:

    var q = gc.Cypher
        .Match("(movie:Movie)")
        .OptionalMatch("(movie)<-[r]-(person:Person)")
        .Return(() => new
        {
            Movie = Return.As<Node<string>>("movie"),
            Roles = Return.As<IEnumerable<string>>("collect([person.name, head(split(lower(type(r)), '_')), r.roles])")
        });
    
        var res = q.Results;
    

    You could either JsonConvert.DeserializeObject<dynamic>() the Movie node, at your leisure, or write a strongly typed class.

    In terms of a 'dynamic' object, I don't know how you were wanting to interact with the collect part of the return statement, if this doesn't help, you might need to update the question to show a usage expectation.