Search code examples
neo4jcypherneo4jclient

Add temporary property to node in Neo4j Cypher for return


I am using the Neo4j .Net client to interface with a Neo4j database.

I often come by this issue when trying to map related nodes to nested classes :

For example mapping

(:Foo)-[:FOOBAR]->(:Bar)

to the form

public class Foo {
    public string FooPropOne { get; set; }
    public string FooPropTwo { get; set; }
    public List<Bar> Bars { get; set; }  
}

public class Bar {
    public string BarPropOne { get; set; }
    public string BarPropTwo { get; set; }
}

To get the correct output for deserialzation I have to write the query like so:

Foo foo = WebApiConfig.GraphClient.Cypher
    .Match("(f:Foo)-[:FOOBAR]->(b:Bar)")
    .With("@{
                FooPropOne: f.FooPropOne,
                FooPropTwo: f.FooPropTwo,
                Bars: collect(b)
             } AS Result")
    .Return<Foo>("Result")
    .SingleOrDefault();

To a certain extent this is fine, and works perfectly, but becomes quite unwieldy the more properties the Foo class has.

I was wondering if there was something I can do in the "With" statement to add a temporary property (in the above case "Bars") when returning the nodes, that does not require me to write every single property on the parent node?

Something like

.With("f.Bars = collect(b)")
.Return<Foo>("f")

that wont actually affect the data stored in the DB?

Thanks in advance for any relpies!!


Solution

  • I don't know of a way to add a temporary property like you want, personally - I would take one of two approaches, and the decision really comes down to what you like to do.

    Option 1

    The choosing properties version

    var foo = client.Cypher
        .Match("(f:Foo)-[:FOOBAR]->(b:Bar)")
        .With("f, collect(b) as bars")
        .Return((f, bars) => new Foo
        {
            FooPropOne = f.As<Foo>().FooPropOne,
            FooPropTwo = f.As<Foo>().FooPropTwo,
            Bars = Return.As<List<Bar>>("bars")
        }).Results.SingleOrDefault();
    

    This version allows you to be Type Safe with your objects you're bringing out, which on a class with lots of properties does limit the chance of a typo creeping in. This will give you back a Foo instance, so no need to do any other processing.

    Option 2

    The little bit of post-processing required version

    var notQuiteFoo = client.Cypher
        .Match("(f:Foo)-[:FOOBAR]->(b:Bar)")
        .With("f, collect(b) as bars")
        .Return((f, bars) => new
        {
            Foo = f.As<Foo>(),
            Bars = bars.As<List<Bar>>()
        }).Results;
    

    This saves on the typing out of the properties, so in a big class, a time saver, and also means if you add extra properties you don't need to remember to update the query. However you do need to do some post-query processing, something like:

    IEnumerable<Foo> foos = notQuiteFoo.Results.Select(r => {r.Foo.Bars = r.Bars; return r.Foo;});
    

    Personally - I like Option 2 as it has less potential for Typos, though I appreciate neither is exactly what you're after.