In some code I am writing, I build a query dynamically, if I capture theParams
which is a Dictionary <string, object>
and the query
strings and move them to the browser, the query fetches what I need (in the example I am running, it is one record).
In the c# code ... I get nothing (fetched == false
), any ideas?
retVal.AddRange(await session.ExecuteReadAsync(async tx =>
{
var theParams = queryParams.Build();
var cursor = await tx.RunAsync(query, theParams);
var fetched = await cursor.FetchAsync();
while (fetched)
{
... etc
So I have 3 options, if i hardcode the params, <string,object> and <object,object> :
List<string> aParams = [ "c8849ba9-8c87-4a55-9db4-2c2cebbccab1", "a2594625-077e-423b-b3a0-eef12e89824b", "8bc17789-dc82-49f6-94a3-ce2615e41e83", "31d6967b-fcfb-4818-ba8d-8adb0cabacd1", "52a1e6f1-6299-41c1-8fef-d1a044fe064e", "232f8c3d-2ab5-4b28-94e8-d1757063d6f6", "baa97f28-8d06-4fbd-96fa-30089d8fd5ee" ];
var testParams = new
{
theTarget = "54:6b65977c-9dd8-4989-80f4-225b96258645:12750",
theAsset = asset,
var0 = "fe9bb638-c729-4c2b-ba10-6ecba0548aac",
var1 = "b8a0feab-355e-4dc0-9117-837f7a206130",
var2 = "437b6bba-f503-4b40-9710-08d44cf18f2c",
params3 = aParams
};
If I pass the testParams
to the query, I get the expected results; if I pass theParams
, I get 0.
Debugging, testParams
works, queryParams._parameters
which is <string,object>
returns empty, theParams
(an attempt to do <object, object>
) throws an exception
AFAIK, C# has no way of building dynamic objects programmatically, and according to the documentation Neo4J should accept a <string, object>
I have managed to narrow it down to the bug being in a param entry that is an array of strings, in this case $params3 : [ uuid1 ... uuid7 ]
is when the query is not returning anything:
MATCH (R3) WHERE R3.uuid IN $params3 WITH COLLECT(R3) as rOps3
OPTIONAL MATCH (RET)-[:language*0..1]->(V3)
MATCH (:Asset {uuid: $theTarget})<-[partOf]-(RET:Asset { uuid: $theAsset })
WHERE V3 IN rOps3
RETURN DISTINCT RET.uuid AS uuid, RET.publicName AS name
If I execute the query in the browser with the :params
set, it works.
The rest of the query are scalar variables, which seem to work (although some of those alone return nothing - which was expected), the $params3
is the key parameter anyway.
More clues on the issue.
When fixed anonymous object:
List<string> aParams = [ "c8849ba9-8c87-4a55-9db4-2c2cebbccab1", "a2594625-077e-423b-b3a0-eef12e89824b", "8bc17789-dc82-49f6-94a3-ce2615e41e83", "31d6967b-fcfb-4818-ba8d-8adb0cabacd1", "52a1e6f1-6299-41c1-8fef-d1a044fe064e", "232f8c3d-2ab5-4b28-94e8-d1757063d6f6", "baa97f28-8d06-4fbd-96fa-30089d8fd5ee" ];
var testParams = new
{
theTarget = "54:6b65977c-9dd8-4989-80f4-225b96258645:12750",
theAsset = "54:6b65977c-9dd8-4989-80f4-225b96258645:13256",,
var0 = "fe9bb638-c729-4c2b-ba10-6ecba0548aac",
var1 = "b8a0feab-355e-4dc0-9117-837f7a206130",
var2 = "437b6bba-f503-4b40-9710-08d44cf18f2c",
params3 = aParams
};
The type of params3 is List<string>
In a real case when building queries and params dynamically, a parameter is just another object, either a scalar object (e.g. string) which seems to work fine or a list of objects (e.g. list), and the object inside the list could be a scalar type (i assume consistency) or a hierarchy of parameters (e.g. Dictionary<string, object>
).
Not sure about the behaviour when one of the params is nesting params as a Dictionary<string,object>
, but not currently building one.
@dana_canzano any idea who is your dotnet driver guru ? :)
Heylo,
Not a guru, but happy to take a look.
So!
- What version of the driver are you using?
- Can you give a full working code snippet that replicates the issue - I mean - I can attempt to recreate - but I suspect you're 90% there already and we can try to see what's happening faster that way.
All the best
Charlotte
Driver 5.28.1
Not sure how to send you all the code ...
// the method used for testing parameter building from a Dictionary of <string, object>
private readonly Dictionary<string, object> _parameters = new Dictionary<string, object>();
public object BuildTmp()
{
// todo fix error when new drivers are out, temporary method
// ---------------------------------------
List<string> conv(object obj)
{
return ((IEnumerable<object>)obj).Select(o => o?.ToString())
.ToList();
}
var list3 = new List<string>();
if (_parameters.ContainsKey("params3"))
list3 = conv(_parameters["params3"]);
var testParams = new
{
theTarget = _parameters.ContainsKey("theTarget") ? _parameters["theTarget"] as string : "",
theAsset = _parameters.ContainsKey("theAsset") ? _parameters["theAsset"] as string : "",
var0 = _parameters.ContainsKey("var0") ? _parameters["var0"] as string : "",
var1 = _parameters.ContainsKey("var1") ? _parameters["var1"] as string : "",
var2 = _parameters.ContainsKey("var2") ? _parameters["var2"] as string : "",
params3 = list3,
};
return testParams;
}
// the compiled version
List<string> aParams = [ "c8849ba9-8c87-4a55-9db4-2c2cebbccab1", "a2594625-077e-423b-b3a0-eef12e89824b", "8bc17789-dc82-49f6-94a3-ce2615e41e83", "31d6967b-fcfb-4818-ba8d-8adb0cabacd1", "52a1e6f1-6299-41c1-8fef-d1a044fe064e", "232f8c3d-2ab5-4b28-94e8-d1757063d6f6", "baa97f28-8d06-4fbd-96fa-30089d8fd5ee" ];
var testParams = new
{
theTarget = asst.Id,
theAsset = asset,
var0 = "fe9bb638-c729-4c2b-ba10-6ecba0548aac",
var1 = "b8a0feab-355e-4dc0-9117-837f7a206130",
var2 = "437b6bba-f503-4b40-9710-08d44cf18f2c",
params3 = aParams
};
// hardcoded parameter builder
var theParams = queryParams.BuildTmp();
// query using only scalar
query = "MATCH (:Asset {uuid: $theTarget})<-[partOf]-(RET:Asset { uuid:$theAsset })\nRETURN DISTINCT RET.uuid AS uuid, RET.publicName AS name \n";
var Qcursor = await tx.RunAsync(query, theParams);
var Qfetched = await Qcursor.FetchAsync();
var Qtemp = await tx.RunAsync(query, testParams);
var Qbfetched = await Qtemp.FetchAsync();
// query using scalar and list of scalar
query = "MATCH (R3) WHERE R3.uuid IN $params3 WITH COLLECT(R3) as rOps3\nOPTIONAL MATCH (RET)-[:language*0..1]->(V3)\nMATCH (:Asset {uuid: $theTarget})<-[partOf]-(RET:Asset { uuid: $theAsset })\n\nWHERE V3 IN rOps3\nRETURN DISTINCT RET.uuid AS uuid, RET.publicName AS name ";
var cursor = await tx.RunAsync(query, theParams);
var fetched = await cursor.FetchAsync();
var temp = await tx.RunAsync(query, testParams);
var bfetched = await temp.FetchAsync();
Right, I'm not super clear as to what the problem is, so maybe we need to step back a bit, and simplify the code so it can be easily executed.
Is there any chance you can convert this to fail in the way you are talking about?
async Task Main()
{
//Use the :play movies graph to execute this code against.
var driver = GraphDatabase.Driver("neo4j://localhost:7687", AuthTokens.Basic("neo4j", "neo4jneo4j"));
List<string> names = ["Tom Cruise", "Tom Hanks", "Keanu Reeves"];
//The '.Dump()' methods are from LinqPad - and are just a quick way to print the results out
(await AreInDb(driver, names)).Dump("In DB");
(await AreInTheMatrix(driver, names)).Dump("In Matrix");
}
private async Task<IEnumerable<string>> AreInTheMatrix(IDriver driver, List<string> names)
{
var testParams = new
{
names = names,
movie = "The Matrix"
};
var query =
@"MATCH (p:Person) WHERE p.name IN $names
MATCH (m:Movie)<-[:ACTED_IN]-(p)
WHERE m.title = $movie
RETURN DISTINCT p.name";
var results = await driver.ExecutableQuery(query).WithParameters(testParams).ExecuteAsync();
return results.Result.Select(r => r["p.name"]).As<List<string>>();
}
private async Task<IEnumerable<string>> AreInDb(IDriver driver, List<string> names)
{
var testParams = new
{
names = names,
movie = "The Matrix"
};
var query = "MATCH (p:Person) WHERE p.name IN $names WITH COLLECT(p.name) AS people RETURN people";
var results = await driver.ExecutableQuery(query).WithParameters(testParams).ExecuteAsync();
return results.Result.Single()["people"].As<List<string>>();
}
Your code won't generate the problem, the problem is with nested params that are not scalar ( so far ), so this works:
List<string> aParams = [ "c8849ba9-8c87-4a55-9db4-2c2cebbccab1", "a2594625-077e-423b-b3a0-eef12e89824b", "8bc17789-dc82-49f6-94a3-ce2615e41e83", "31d6967b-fcfb-4818-ba8d-8adb0cabacd1", "52a1e6f1-6299-41c1-8fef-d1a044fe064e", "232f8c3d-2ab5-4b28-94e8-d1757063d6f6", "baa97f28-8d06-4fbd-96fa-30089d8fd5ee" ];
var testParams = new
{
theTarget = asst.Id,
theAsset = asset,
var0 = "fe9bb638-c729-4c2b-ba10-6ecba0548aac",
var1 = "b8a0feab-355e-4dc0-9117-837f7a206130",
var2 = "437b6bba-f503-4b40-9710-08d44cf18f2c",
params3 = aParams
};
var temp = await tx.RunAsync(query, testParams);
var fetched = await temp.FetchAsync();
fetched will be true when the query uses params3
public object BuildTmp()
{
// todo fix error when new drivers are out, temporary method
// ---------------------------------------
List<string> conv(object obj)
{
return ((IEnumerable<object>)obj).Select(o => o?.ToString())
.ToList();
}
var list3 = new List<string>();
if (_parameters.ContainsKey("params3"))
list3 = conv(_parameters["params3"]);
var testParams = new
{
theTarget = _parameters.ContainsKey("theTarget") ? _parameters["theTarget"] as string : "",
theAsset = _parameters.ContainsKey("theAsset") ? _parameters["theAsset"] as string : "",
var0 = _parameters.ContainsKey("var0") ? _parameters["var0"] as string : "",
var1 = _parameters.ContainsKey("var1") ? _parameters["var1"] as string : "",
var2 = _parameters.ContainsKey("var2") ? _parameters["var2"] as string : "",
params3 = list3,
};
return testParams;
}
...
var theParams = queryParams.BuildTmp();
var cursor = await tx.RunAsync(query, theParams);
var fetched = await cursor.FetchAsync();
fetched will be false when using params3
In both cases when a query doesn't use params3 will return the same value.
Do you mean because the List<string> names
is defined as an Array?
As in - defining it as a new List<string>()
should give the error?
What would you do to cause the code I supplied to break.
I'm specifically asking about mine - as I know I can compile and run it, and I want to remove all the extraneous details to get to the simplest view possible of the error.
Charlotte
This is the equivalent of a query for the browser like this:
{
"theTarget": '54:6b65977c-9dd8-4989-80f4-225b96258645:12750',
"theAsset": '54:6b65977c-9dd8-4989-80f4-225b96258645:12778',
"var0": 'fe9bb638-c729-4c2b-ba10-6ecba0548aac',
"var1": 'b8a0feab-355e-4dc0-9117-837f7a206130',
"var2": '437b6bba-f503-4b40-9710-08d44cf18f2c',
"params3" : [ 'c8849ba9-8c87-4a55-9db4-2c2cebbccab1',
'a2594625-077e-423b-b3a0-eef12e89824b',
'8bc17789-dc82-49f6-94a3-ce2615e41e83',
'31d6967b-fcfb-4818-ba8d-8adb0cabacd1',
'52a1e6f1-6299-41c1-8fef-d1a044fe064e',
'232f8c3d-2ab5-4b28-94e8-d1757063d6f6',
'baa97f28-8d06-4fbd-96fa-30089d8fd5ee' ]
}
I am not generating them yet, but haven't tested further nested parameters that are complex:
{
"theTarget": '54:6b65977c-9dd8-4989-80f4-225b96258645:12750',
"level1": {
"level1scalar" : ""
"level1nonscalar": {
// etc
}
}
I've also only tested everything being a string (that's all i need for this phase) but I am not sure how it will react with other scalar types.
You can see in the debugging screenshot above that the abstract object defined programmatically works and the debugger shows a List<string>
, whilst the one generated during execution - the debugger even "matches" to the same anonymous type ... but returns false.
I don't get any errors in my code, for the same query I only get that things were fetched or not fetched (when they should have been fetched) depending of which parameters object I pass.
Oh - by error - I meant doesn't get you values - not a compile type thing.
I genuinely do not know what to do to get this to replicate. I have dynamically generated objects - and I still get responses (see below) - using List
etc is working - what am I missing in this code
to get to where you are?
private IDictionary<string, object> _allParams = new Dictionary<string, object>
{
{"movie", "The Matrix"},
{"names", new List<object>{"Tom Cruise", "Tom Hanks", "Keanu Reeves"}}
};
private async Task<IEnumerable<string>> AreInTheMatrix2(IDriver driver, List<string> names)
{
List<string> conv(object obj)
{
return ((IEnumerable<object>)obj).Select(o => o?.ToString())
.ToList();
}
var testParams = new
{
names = conv(_allParams["names"]),
movie = _allParams["movie"]
};
var query =
@"MATCH (p:Person) WHERE p.name IN $names
MATCH (m:Movie)<-[:ACTED_IN]-(p)
WHERE m.title = $movie
RETURN DISTINCT p.name";
var results = await driver.ExecutableQuery(query).WithParameters(testParams).ExecuteAsync();
return results.Result.Select(r => r["p.name"]).As<List<string>>();
}
If I refactor my code to yours:
result has a count of 1
result1 has a count of 0
The debugger detects testParams and theParams as the same AnonymousType29 (as I said theParams is built at runtime).
I have no idea what's the problem, it took me a week to figure it out as I thought it was in my logic further up - and another week to get to the point where the testParams that are written and theParams that are calculated came up with the same type (by luck - i was just expecting the same internal types on the debugger).
This is also code to figure out what is going on, in principle the List conversion shouldn't be necessary because the parameters could be anything (string/int/etc) and List is what should go as Params (somehow it happens internally).
You could wrap that code you have as mine inside the dynamic method, rather than a full detail to see if it makes a difference:
public object BuildTmp()
{
List<string> conv(object obj)
{
return ((IEnumerable<object>)obj).Select(o => o?.ToString())
.ToList();
}
var list3 = new List<string>();
list3 = conv(_parameters["params3"]);
var testParams = new
{
theTarget = _parameters.ContainsKey("theTarget") ? _parameters["theTarget"] as string : "",
theAsset = _parameters.ContainsKey("theAsset") ? _parameters["theAsset"] as string : "",
var0 = _parameters.ContainsKey("var0") ? _parameters["var0"] as string : "",
var1 = _parameters.ContainsKey("var1") ? _parameters["var1"] as string : "",
var2 = _parameters.ContainsKey("var2") ? _parameters["var2"] as string : "",
params3 = list3,
};
return testParams;
}
}
I mean - I'm running it with this to generate the params, and it's still behaving:
private object GenerateParams()
{
List<string> conv(object obj)
{
return ((IEnumerable<object>)obj).Select(o => o?.ToString())
.ToList();
}
var names = new List<string>();
names = conv(_allParams["names"]);
var testParams = new
{
names = names,
movie = _allParams.ContainsKey("movie") ? _allParams["movie"] as string : string.Empty
};
return testParams;
}
As far as I can tell that's the same as what you're doing - can you see anything I'm missing on that?
The errors you're getting are odd - can you expand the 'list3' in your debugger to confirm they have the same files in them?
Yeah they do, I originally thought it was a mistake in the generation (earlier in my debugging i was generating a query to run in the browser step-by-step)
One further step I got from your code:
Both return one:
query = "MATCH (R3) WHERE R3.uuid IN $params3 WITH COLLECT(R3) as rOps3 RETURN rOps3";
Second one returns zero:
query = "MATCH (R3) WHERE R3.uuid IN $params3 WITH COLLECT(R3) as rOps3\nOPTIONAL MATCH (RET)-[:language*0..1]->(V3)\nMATCH (:Asset {uuid: $theTarget})<-[partOf]-(RET:Asset { uuid: $theAsset })\n\nWHERE V3 IN rOps3\nRETURN DISTINCT RET.uuid AS uuid, RET.publicName AS name ";
Hello @charlotte.skardon - did you manage to replicate when the query is more complex?