DotNet4Neo4j / Neo4jClient

.NET client binding for Neo4j
https://www.nuget.org/packages/Neo4jClient
Microsoft Public License
431 stars 146 forks source link

How to retrieve relationship with its metadata #451

Closed agecas closed 1 year ago

agecas commented 1 year ago

A working example as I am debugging Neo4jCLient lib now, this is running against local docker container with movies sample DB:


using System;
using System.Collections.Generic;
using Neo4jClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

var uri = new Uri("bolt://localhost:7687");
var client = new BoltGraphClient(uri, "neo4j", "pasw123")
{
    JsonContractResolver = new CamelCasePropertyNamesContractResolver()
};

await client.ConnectAsync();

var result = await client.Cypher.Match("(a:Person { name: 'Tom Hanks' })-[r]->(m)").Return((a, r, m) =>
new {
    relationship = r.As<RelationshipInstance<Relationship>>()
}).ResultsAsync;

Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));

public sealed class Relationship
{
    public List<string> Roles { get; set; }
}

Full exception message:

Unhandled exception. System.ArgumentException: Neo4j returned a valid response, however Neo4jClient was unable to deserialize into the object structure you supplied.

First, try and review the exception below to work out what broke.

If it's not obvious, you can ask for help at http://stackoverflow.com/questions/tagged/neo4jclient

Include the full text of this exception, including this message, the stack trace, and all of the inner exception details.

Include the full type definition of <>f__AnonymousType01[[Neo4jClient.RelationshipInstance1[[Relationship, Neo4jSample.ConsoleApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], Neo4jClient, Version=4.0.4.0, Culture=ne utral, PublicKeyToken=null]].

Include this raw JSON, with any sensitive values replaced with non-sensitive equivalents:

{"columns":["relationship"],"data":[[{"data":{"roles":["Jim Lovell"]}}]]} (Parameter 'content') ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentNullException: Value cannot be null. (Parameter 'uriString') at System.Uri..ctor(String uriString) at Neo4jClient.ApiModels.RelationshipApiResponse1.GetLastPathSegment(String uri) in D:\Work\github\Neo4jClient\Neo4jClient\ApiModels\RelationshipApiResponse.cs:line 48 at Neo4jClient.ApiModels.RelationshipApiResponse1.ToRelationshipInstance(IGraphClient client) in D:\Work\github\Neo4jClient\Neo4jClient\ApiModels\RelationshipApiResponse.cs:line 28 --- End of inner exception stack trace --- at System.RuntimeMethodHandle.InvokeMethod(Object target, Span1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) at Neo4jClient.Serialization.CypherJsonDeserializer1.b12_6(Object n) in D:\Work\github\Neo4jClient\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 214 at Neo4jClient.Serialization.CommonDeserializerMethods.MutateObject(DeserializationContext context, JToken value, IEnumerable1 typeMappings, Int32 nestingLevel, TypeMapping mapping, Type propertyType) in D:\Work\github\Neo4jCl ient\Neo4jClient\Serialization\CommonDeserializerMethods.cs:line 343 at Neo4jClient.Serialization.CommonDeserializerMethods.CoerceValue(DeserializationContext context, PropertyInfo propertyInfo, JToken value, IEnumerable1 typeMappings, Int32 nestingLevel) in D:\Work\github\Neo4jClient\Neo4jClie nt\Serialization\CommonDeserializerMethods.cs:line 210 at Neo4jClient.Serialization.CypherJsonDeserializer`1.<>c__DisplayClass22_0.b0(JToken cell, Int32 cellIndex) in D:\Work\github\Neo4jClient\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 5 33 at System.Linq.Enumerable.SelectIterator[TSource,TResult](IEnumerable1 source, Func3 selector)+MoveNext() at System.Collections.Generic.LargeArrayBuilder1.AddRange(IEnumerable1 items) at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 source) at Neo4jClient.Serialization.CypherJsonDeserializer1.ReadProjectionRowUsingCtor(DeserializationContext context, JToken row, IDictionary2 propertiesDictionary, IList1 columnNames, IEnumerable1 jsonTypeMappings, ConstructorIn fo ctor) in D:\Work\github\Neo4jClient\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 525 at Neo4jClient.Serialization.CypherJsonDeserializer1.<>c__DisplayClass21_1.<ParseInProjectionMode>b__5(JToken token) in D:\Work\github\Neo4jClient\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 488 at System.Linq.Enumerable.SelectEnumerableIterator2.ToArray() at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 source) at Neo4jClient.Serialization.CypherJsonDeserializer1.Deserialize(String content, Boolean isHttp) in D:\Work\github\Neo4jClient\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 76 --- End of inner exception stack trace --- at Neo4jClient.Serialization.CypherJsonDeserializer1.Deserialize(String content, Boolean isHttp) in D:\Work\github\Neo4jClient\Neo4jClient\Serialization\CypherJsonDeserializer.cs:line 110 at Neo4jClient.BoltGraphClient.ParseResults[TResult](IEnumerable1 result, CypherQuery query) in D:\Work\github\Neo4jClient\Neo4jClient\BoltGraphClient.cs:line 395 at Neo4jClient.BoltGraphClient.Neo4jClient.IRawGraphClient.ExecuteGetCypherResultsAsync[TResult](CypherQuery query) in D:\Work\github\Neo4jClient\Neo4jClient\BoltGraphClient.cs:line 365 at Program.

$(String[] args) in D:\Work\trash\Neo4jSample\Neo4jSample.ConsoleApp\Program.cs:line 17 at Program.
(String[] args)

My original problem is, that I need to retrieve Node ID's and Start/End ID's in order to build a graph in a custom UI based on given business rules. Simply doing this

var result = await client.Cypher.Match("(a:Person { name: 'Tom Hanks' })-[r]->(m)").Return((a, r, m) =>
new {
    relationship = r.As<Relationship>()
}).ResultsAsync;

works fine, but it only returns relationship properties, but not the ID's that I can use to connect nodes together. Am I doing something wrong or is there a better way to extract graph specific data + custom properties?

agecas commented 1 year ago

Specifically what I am after is response like Neo4J browser gives

Node

{ "identity": 71, "labels": [ "Person" ], "properties": { "born": 1956, "name": "Tom Hanks" } }

Relationship

{ "identity": 202, "start": 71, "end": 144, "type": "ACTED_IN", "properties": { "roles": [ "Jim Lovell" ] } }

Properties as as Identity/Start/End allows me to connect NODES in cases where query has things like [r:TYPE1..20] or so, as this returns a path with multiple hops and without ID's I am not sure how else this could be achieved.

Btw same error happens if I use .Node() on any of the nodes, as per sample below (produces same error):

var result = await client.Cypher.Match("(a:Person { name: 'Tom Hanks' })-[r]->(m)").Return((a, r, m) =>
new {

    relationship = r.As<MyRelationship>(),
    type = Return.As<string>("type(r)"),
    startNode = Return.As<Node<string>>("startNode(r)"),
    endNode = Return.As<Node<string>>("endNode(r)"),
}).ResultsAsync;
cskardon commented 1 year ago

So, you should ignore what the browser returns, as that's doing extra work in the backend, the only way I know to get the information you're after is to use PathsResultBolt and return Paths instead of just nodes.

Something like:

    var query =
        client.Cypher
        .Match("p=(a:Person { name: 'Tom Hanks' })-[r]->(m)")
        .Return(p => p.As<PathsResultBolt>());
agecas commented 1 year ago

Awesome, thats exactly what I am after. How would I use Node or RelationshipInstance or these are for internal use only, as I can't find any examples in the docs?