Neo4jClient - Serializing response all attributes

Hi,

When i use "neo4j browser", i see the below response...
When i use Neo4jClient with .Net, i send the desired class but it serializes only the "properties" section and not all attributes. In this example i have a relationship and i want to extract also the attributes:
"startNodeElementId": "836369",
"endNodeElementId": "566040"

how can i do that?
Thanks

[From Neo4j Browser]
{

"identity": 879798,

"start": 836369,

"end": 566040,

"type": "DEVELOPER",

"properties": {

"relTypeName": "Developer",

"relTypeId": 38,

"personIdNumber": "123456789",

"departmentNumber": 123

},

"elementId": "879798",

"startNodeElementId": "836369",

"endNodeElementId": "566040"

}

So, when you call via an application you only get back what you asked for. The browser does extra work behind the scenes to show you that data.

If you want it - you need to ask for it explicitly, there's a couple of ways - depending on how you want to, in both cases I'll be using this class as an output:

public class ActedIn
{
    [JsonProperty("role")]
    public string Role {get;set;}
    
    public long Id {get;set;}
    public long StartId {get;set;}
    public long EndId { get;set;}
    public string Type {get; set;}
}

Option 1 - Do the work in Return:

var query = client.Cypher
    .Match("(:Movie {title:'Jumanji'})<-[r:ACTED_IN]-(p:Person)")
    .Return(r => new ActedIn
    {
        Id = r.Id(),
        StartId = Return.As<long>("id(startNode(r))"),
        EndId = Return.As<long>("id(endNode(r))"),
        Type = Return.As<string>("type(r)"),
        Role = r.As<ActedIn>().Role
   });

(await query.ResultsAsync).Dump("Using Return");

Option 2 - Use a WITH to structure it how you want first:

var query2 = client.Cypher
    .Match("(:Movie {title:'Jumanji'})<-[r:ACTED_IN]-(p:Person)")
    .With("{Id: id(r), StartId: id(startNode(r)), EndId: id(endNode(r)), Type: type(r), role: r.role} AS relationship")
    .Return(relationship => relationship. As<ActedIn>());

(await query2.ResultsAsync).Dump("Using With");

Which outputs like this:

image

I guess you wouldn't want the 'Type' 0 but it's there for show...

All the best

Charlotte

Hi, Thanks for your reply but...
My query is a little more complicated.
I want a person with a collection of related nodes, so my query is: (working but without the "internal" attributes)

var query = graphClient.Cypher
.Match("(p:Person)-[r]->(a:Auto)")
.With("p, collect(a) as auto, collect(r) as rel")
.OrderByDescending("size(auto)")
.Return((p, auto, rel) => new
{
person = p.As(),
autos = auto.As<IEnumerable>(),
rel = rel.As<IEnumerable>()
})
.Limit(limit);

Okey dokes, the principal is exactly the same as above, in your .With you still do the mapping:

var query = client.Cypher
    .Match("(p:Person)-[r]->(movie:Movie)")
    .With("p, collect({Movie: movie, Id: id(movie)}) AS movies, collect({EndId: id(endNode(r))}) AS rel")
    .OrderByDescending("size(movies)")
    .Return((p, movies, rel) => new
    {
        person = p.As<string>(),
        movies = movies.As<IEnumerable<MovieOut>>(),
        rel = rel.As<IEnumerable<string>>()
    })
    .Limit(limit);

(await query.ResultsAsync).Dump("Actual");

In this case, I've also added these two C# POCOs for output, obvs - you'd use your own:

public class MovieOut
{
    public Movie Movie {get; set;}
    public long Id {get; set;}
}

public class Movie
{
    [JsonProperty("title")]
    public string Title {get; set;}
}

Which looks like this:

I'm just outputting the 'Person' as a string - so again, use your own POCO, but the rel is showing the Ids of the movies output.

Charlotte

Hi Charlotte - i have a similar query, but i have a relationship like this:

<-[r:partOf*0..]-

I keep getting:

Type mismatch: expected Relationship but was List

You have a variable length pattern. I believe it is a deprecated feature now, but the variable ‘r’ may be a list of relations to represent the relationships along the path. The collect is across rows of results. You are trying to collect the endNode of ‘r’. The endNode() method expects a single relationship. In your case ‘r’ is a list.

What information do you need? Do you want the endNode of each relationship of this type along the path, or do you want just the endNode of the last relationship along the path?

i need the 'current node' (n) and it's information and a list of all ids (of the nodes connecting). So the equivalent of endNode for each entry on that list.

I have done that if I have 1 node as a reference, but this one is traversing.

It worries me that my payload is unnecessarily large (I only need endNodeElementId)

[[
{
  "identity": 134,
  "start": 6468,
  "end": 6335,
  "type": "partOf",
  "properties": {

  },
  "elementId": "5:d2fb2e08-d9a8-4ace-b7b6-305175ee9551:134",
  "startNodeElementId": "4:d2fb2e08-d9a8-4ace-b7b6-305175ee9551:6468",
  "endNodeElementId": "4:d2fb2e08-d9a8-4ace-b7b6-305175ee9551:6335"
}

For reference - my user can come at any assigned node on a graph.

He can only see the ancestors (which could be a single node list .. or a graph) and the graph for all the descendants.

I split the query into 2 (ancestors/descendants) and do a union (good stuff, no problem).

Since I have all the information on each node from the query, I only need to know the list of elementId's to which that node is connected. Looking at the payload of information I need, it would turn that the relationships JSON is as big (or bigger) than the actual node's information (inefficient for thousands of nodes), plus involves extra unnecessary processing of the relationships' envelope (waste of CPU).

Do you need the path? Or is it purely the Ids of those nodes you need?

The are multiple ways to accomplish this, but let’s pick the one closer to what you were doing. Let’s say your match pattern is as follows, where ‘n’ is your current node and you want the elementIds of the nodes along the path.

(n)<-[r:partOf*0..]-(m)

Let’s say ‘n’ is attached to a chain of 5 nodes. Your match will return 6 rows back, one for ‘n’ (since your lower bound is zero), and a row for each segment, up to the max length of 5 nodes. In each case, ‘m’ will always represent the last node of the chain for each segment. This means the collection of nodes ‘m’ will be equivalent to all the nodes attached to ‘n’ along the path of max length. This sounds like what you want.

The following should work:

match (n{id:100})
match (n)<-[:partOf*0..]-(m)
return n, collect(elementId(m)) 

If there is a graph off of ‘n’ and there could common nodes along some oth these paths (like in the bottom part of your diagram), you can eliminate collecting duplicate elementIds by including ‘distinct’.

match (n{id:100})
match (n)<-[:partOf*0..]-(m)
return n, collect(distinct elementId(m)) 

You may want to review the apoc path methods if you are querying for subgraphs from a root node.

Just a node, because of your min length of zero, I believe the collection will contain ‘n’ as well. Are you using zero because there may not be any connected nodes and you want to guarantee a result?

just the endNodeElementId for each of the relationships would be fantastic (and the C# would be cleaner).

yes - you could have only 1 node ('the top of a hierarchy')

this one gives me only n, list of elementID

I need (since it is a 'partOf') from node n

n-3, null
n-2, [parent list]
n-1, [parent list]
n [parent list] <--- this is my starting point
n+1, [parent list]

I already have a query that gives me what I want, except that the relationships list has a lot of attributes I don't need - I only need endNodeElementId

MATCH (p)<-[r:partOf*0..]-(a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})
  WITH p, elementId(p) AS uuid, collect(r) AS rel
  RETURN uuid,
         rel
UNION
MATCH (a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})<-[r:partOf*0..]-(p:NodeType) 
  WITH p, elementId(p) AS uuid, collect(r) AS rel
  RETURN uuid, 
         rel

Ok, if ‘r’ is a list, you can extract the elementIds (and an other info you want) using list comprehension.

MATCH (p)<-[r:partOf*0..]-(a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})
  WITH p, elementId(p) AS uuid, [i in r | elementId(i)] AS rel
  RETURN uuid,
         rel
UNION
MATCH (a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})<-[r:partOf*0..]-(p:NodeType) 
  WITH p, elementId(p) AS uuid, [i in r | elementId(i)] AS rel
  RETURN uuid, 
         rel

Binding a variable to a variable length path and using it as you are is a deprecated feature. Here is a solution that doesn’t rely on ‘r’.

MATCH path=(p)<-[:partOf*0..]-(a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})
  WITH p, elementId(p) AS uuid, [i in relationships(path) where type(i)=“partOf”| elementId(i)] AS rel
  RETURN uuid,
         rel
UNION
MATCH path=(a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})<-[:partOf*0..]-(p:NodeType) 
  WITH p, elementId(p) AS uuid, [i in relationships(path) where type(i)=“partOf”| elementId(i)] AS rel
  RETURN uuid, 
         rel

You don’t need the “where” condition checking for partOf in your case, since you only have one relationship type in your path pattern. I put it in to demonstrate, so you can leverage at a later date when needed. You can remove it and use this instead.

[i in relationships(path) | elementId(i)]

List comprehension is one feature I use a lot.

Map projection is another one to learn.

I read the list comprehension documentation, but the examples appeared that it would work only on the node side (I know relationships are nodes), I just couldn't articulate the statement

This was what I was trying to find out - amazing

That is great. Glad you got it.

Just to clarify, nodes are nodes and relationships are relationships. They are not the same, but they are both entities. All entities have an elementId (which used to be id) and properties. Relationships also have a start node and end node.

Misunderstood the output of the query:

I am now getting the id of the relationship node - I need the endNodeElementId property inside that node.

{
  "identity": 136,
  "start": 6335,
  "end": 6417,
  "type": "partOf",
  "properties": {

  },
  "elementId": "5:d2fb2e08-d9a8-4ace-b7b6-305175ee9551:136",        // getting this
  "startNodeElementId": "4:d2fb2e08-d9a8-4ace-b7b6-305175ee9551:6335",
  "endNodeElementId": "4:d2fb2e08-d9a8-4ace-b7b6-305175ee9551:6417" // need this one
}

Nonetheless I have moved 1 step closer:

But analysing the results, I realised that:

  • In the top query I need the .startNodeElementId (p = endNodeElementId)
  • In the bottom query I need the .endNodeElementId (p = startNodeElementId)

Still, the payload of the relationship (JSON) seems unreasonably large, I would like something that gives me:

[relationships(path)][0][0].startNodeElementId
[relationships(path)][0][size(relationships(path))-1].endNodeElementId

At this stage, I have to create a temporary list with the results and figure out which attribute from the relationship I need to point to ...

Still think I am transmitting 25% of things I won't check - a waste of bandwidth.

(I hope I am not too annoying, tbh I find this fascinating) :stuck_out_tongue_winking_eye:

You are not being annoying: we are here to help. Also, helping with these is like doing daily puzzles. I prefer this to sudoku.

If you want the node elementIds, you can get them from the path.


MATCH path=(p)<-[:partOf*0..]-(a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})
  WITH p, elementId(p) AS uuid, [i in tail(nodes(path))| elementId(i)] AS nodeIds
  RETURN uuid,
         nodeIds
UNION
MATCH path=(a:NodeType {uuid:'ae66e077-f7ea-410d-9112-5160c396dfa0'})<-[:partOf*0..]-(p:NodeType) 
  WITH p, elementId(p) AS uuid, [i in tail(nodes(path))| elementId(i)] AS nodeIds
  RETURN uuid, 
         nodeIds

Tail returns a new list with the first element removed.

Let me do some testing to see one of the node(path) has to be reversed to avoid including ‘p’ and keeping the order correct. If would just be a matter of replacing nodes(path) with reverse(nodes(path)).

1 Like

I created this sample data:

create(p:Person{name:"Alice"}),(p1:Person{name:"stewart"}),(p2:Person{name:"Bob"}),(p3:Person{name:"Sam"}),(p4:Person{name:"Scott"}),(p5:Person{name:"Bill"}),(p6:Person{name:"Roger"})
create(p)<-[:PartOf]-(p1)<-[:PartOf]-(p2)<-[:PartOf]-(p3)
create(p4)<-[:PartOf]-(p5)<-[:PartOf]-(p6)<-[:PartOf]-(p)
return *

Query:

match path=(p:Person)<-[:PartOf*0..]-(a:Person{name:"Alice"})
return 1 as query, p.name, [i in nodes(path) | i.name] as pathNames
union
match path=(a:Person{name:"Alice"})<-[:PartOf*0..]-(p:Person)
return 2 as query, p.name, [i in nodes(path) | i.name] as pathNames

I used names here instead of elementId so I could identify the nodes. Is this what you want? For each node along both paths you want that node and its nodes back to the specified node? Note, each row is a subset of the longest row. How about just getting the longest chain instead of all the intermediate ones?

In case you don't want the 'p' node in the list of nodes, you can use list indexing to filter them out:

match path=(p:Person)<-[:PartOf*0..]-(a:Person{name:"Alice"})
return 1 as query, p.name, [i in nodes(path)[1..] | i.name] as pathNames
union
match path=(a:Person{name:"Alice"})<-[:PartOf*0..]-(p:Person)
return 2 as query, p.name, [i in nodes(path)[..-1] | i.name] as pathNames