Chris Tophski
Chris Tophski

Reputation: 960

How to deserialize a relationship from a Cypher query, retaining a dynamic relationship type?

Let's start with an example data set. There are 2 nodes and 2 relationships with differing relationship types between them. Both nodes and relationships have properties. Don't focus too much on the semantics, this is derived from a use case I cannot show here directly.

create (a:AlphaNode {name: "Alice"} )
create (b:BetaNode {name: "Bob"} )
create (a)-[:HAS {really: false} ]->(b)
create (a)-[:NEEDS {timespan: 42} ]->(b)

Now, I would like to run a query along the following lines:

match (a:AlphaNode)-[r:HAS|NEEDS]->(b:BetaNode)
// where etc ... (not important in the example)
return a, r, b

This will return 2 result sets, where the nodes are identical, only the relationship denoted by r will differ between the result sets.

My C# code using Neo4jClient should look like this:

client.Cypher
    .Match("(a:AlphaNode)-[r:HAS|NEEDS]->(b:BetaNode)")
    .ReturnDistinct((a, r, b) => new
    {
        A = a.As<AlphaNode>(),
        R = r.As<SomeRelationship>(),
        B = b.As<BetaNode>()
    })
    // .ResultAsync etc. (not important, here)
    ;

Now, I'm running into problems with SomeRelationship. Surely, I could make it a POCO/DTO, but then I wouldn't have access to the relationship type (HAS or NEEDS). I can't find a good hint or example in the docs and all I've found are hints towards the classes Relationship, RelationshipInstance, RelationshipReference (which would have a relationship type, but this is not read from the database, but rather fixed by class inheritance), but I can't really make them (or their descendants, to be more precise) work like this (even for a single relationship, but the dynamics are the actual point, here).

Is there a way to do this more cleanly with a dynamic relationship POCO than with some weird inheritance that is mainly there in order to replace a constructor with a parameterless one? I think the intentions for these classes are a bit different from what I expect. Don't get me wrong, I'd love to inherit a relationship-ish base class, but the base class would have to provide the relationship type from the response and have a parameterless constructor in the first place.

Upvotes: 0

Views: 47

Answers (1)

dandeto
dandeto

Reputation: 846

I'll throw an answer out there because I ran into a similar problem that you have. I have a couple of solutions depending on what you need.

Return Property Dictionary and Labels

This method requires you to modify the returned anonymous object to accommodate all of the relationship properties in a Dictionary<string, string> and the type of relationship as a string.

client.Cypher
    .Match("(a:AlphaNode)-[r:HAS|NEEDS]->(b:BetaNode)")
    .ReturnDistinct((a, r, b) => new
    {
        A = a.As<AlphaNode>(),
        RelationshipProperties = r.CollectAs<Dictionary<string, string>>(),
        RelationshipType = r.Type(),
        B = b.As<BetaNode>()
    }).ResultsAsync;

Return as JSON

Likely overkill for your situation, but for my use-case it was necessary. You can return the entire path (both nodes and the relationship) as a JSON string by using the following query. Then, you can serialize it into your models however you want.

client.Cypher
    .Match("path = (a:AlphaNode)-[r:HAS|NEEDS]->(b:BetaNode)")
    .Return<string>("path")
    .ResultsAsync;

Upvotes: 0

Related Questions