Search code examples
c#neo4jneo4jclient

Neo4jClient Cypher query collect statement with multiple values


i'm trying to convert a query from cypher to use neo4jclient api in c#

Here is my cypher

start server=node:node_auto_index(serverId='SHO2K3MS49')
MATCH 
 server-[:IS_SERVER_TYPE]->type,
 appEnv-[:HAS_SERVER]->server,
 app-[:HAS_ENV]->appEnv
return        
   server.serverId,
   collect([
   appEnv.environmentTypeId,
   appEnv.appEnvId,
   app.appId,
   app.appName
  ]) ;

The query returns one line per server and collects all the applications on that server.

The .CollectAs api as far as i can see only allows single values.

An idea how to do this with the .net api?

EDIT

i've just tried this query

_connectedClient
 .Cypher                 
 .Start(new {server = Node.ByIndexLookup("node_auto_index", "serverId", "SHO2K3MS49") })
 .Match("server-[:IS_SERVER_TYPE]->type", "appEnv-[:HAS_SERVER]->server", "app-[:HAS_ENV]->appEnv")               
 .Return((server, appEnv, app) => 
  new
   {
       ServerName = Return.As<string>("server.serverId"),     
       aa = Return.As<dynamic>    ("collect([appEnv.environmentTypeId,appEnv.appEnvId,app.appId,app.appName])")                                                                                                                          
   })
 .Results;

and received this result.

stacktrace

at Neo4jClient.Serialization.CypherJsonDeserializer`1.Deserialize(String content)
   at Neo4jClient.GraphClient.<>c__DisplayClass1e`1.<Neo4jClient.IRawGraphClient.ExecuteGetCypherResultsAsync>b__1d(Task`1 responseTask)
   at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()

innerexception

Accessed JArray values with invalid key value: "data". Array position index expected

message -- removed boilerplate text for brevity

Neo4jClient encountered an exception while deserializing the response from the server. This is likely a bug in Neo4jClient.

Include the full type definition of <>f__AnonymousType1`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].

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

{"columns":["ServerName","aa"],"data":[["SHO2K3MS49",[["PRD","MATT.PRD","MATT","MATT"],["PRD","ARCSERV.PRD","ARCSERV","ArcServ"],["PRD","ACTIVE DIRECTORY _WINDOWS SERVER NETWORKING_.PRD","ACTIVE DIRECTORY _WINDOWS SERVER NETWORKING_","Active Directory (Windows Server networking)"]]]]}
Parameter name: content

Solution

  • It's because the results you bring back from the collect statement are basically strings with no defining columns. JSON.NET can't infer what the columns are (and you can't use AS to help it), so all you get are strings like:

    "[\r\n"EnvType1",\r\n"AppEnvId1",\r\n"App2",\r\n"App 2"\r\n]"
    

    Which you can get by using the following query:

    _connectedClient
        .Cypher                 
        .Start(new {server = Node.ByIndexLookup("node_auto_index", "serverId", "SHO2K3MS49") })
        .Match("server-[:IS_SERVER_TYPE]->type", "appEnv-[:HAS_SERVER]->server", "app-[:HAS_ENV]->appEnv")               
         .Return((server, appEnv, app) => 
          new
           {
               ServerName = Return.As<string>("server.serverId"),     
               aa = Return.As<IEnumerable<string>>("collect([appEnv.environmentTypeId,appEnv.appEnvId,app.appId,app.appName])")                                                                                                                          
           })
         .Results;
    

    I've changed the return type for the aa property to be IEnumerable<string>.

    Another route would be to use GroupBy after getting the data:

    var query2 = GraphClient
        .Cypher
        .Start(new { server = new NodeReference(1) })
        .Match("server-[:IS_SERVER_TYPE]->type", "appEnv-[:HAS_SERVER]->server", "app-[:HAS_ENV]->appEnv")
        .Return((server, appEnv, app) =>
            new
            {
                ServerId = Return.As<string>("server.ServerId"),
                EnvironmentTypeId = Return.As<string>("appEnv.EnvironmentTypeId"),
                AppEnvId = Return.As<string>("appEnv.AppEnvId"),
                AppId = Return.As<string>("app.AppId"),
                AppName = Return.As<string>("app.AppName"),
            });
    
    var results2 = query2.Results.GroupBy(g => g.ServerId).ToList();
    

    Which I think would give you the results in the way you want them, I guess at issue here is whether it's more performant to do the collect on the server, or to GroupBy on the client...