Wait for cypher query execution completion in Neo4j C# driver

I generally wait for Neo4j to execute a Cypher query as the following.

var exeResult = session.ExecuteWriteAsync(async x =>
{
    var result = await x.RunAsync(GetQuery());
    return await result.ToListAsync();
});
await exeResult;

// or:
// exeResult.Wait();

Few concerns about this approach:

  • Is this the correct way to wait for a Cypher query execution?
  • If the query execution fails (e.g., a typo in the Cypher query), the exception is not caught in the try-catch block that encompasses this snippet; instead, the program exits.
  • If multiple queries are to be executed (e.g., the above block should be repeated for GetQuery(x) where x changes between different queries), the behavior of the above block is hard to predict. Sometimes, it runs to completion; sometimes, the program exists with no error messages while the queries are "partially" executed (i.e., some executed, some errored out). It seems the second query execution starts before the previous one ends, leading to race conditions? or sometimes I get Neo.TransientError.Transaction.DeadlockDetected.

In short, I am unsure if this is the correct way to wait for a query's successful execution.

Why not just use the blocking versions instead of the async versions of read and write transaction functions if you want the blocking behavior?

That seems a good alternative, though I don't have the blocking alternatives. For instance, I only have Driver.AsyncSession and do not have Driver.Session; or I have ExecuteWriteAsync but do not have ExecuteWrite.

I am using Neo4j.Driver version 5.3.0. Should I add a different dependency?

I don't use the .NET driver. I am a java developer. The java driver has blocking, async, and reactive versions.

I did read the following in the .NET docs.

Is that package production-ready? or is it merely for demo purposes?

Additionally, any suggestions on how I can wait while still using the async overloads?

That statement was from the .NET driver doc, so I imagine it is supported. I can try to find out.

In the meantime, I looked at the java driver source code a while back. What I found was the blocking calls where actually using the non-blocking calls, waiting, consuming all the results, and returning the results. Thus, it was doing what you want to do, so I encourage you to look at that library's source code to see how they did it. Maybe you can get some ideas to achieve what you want. Sorry, I am not a C# developer.

That is a good suggestion, thank you!

Hi @hamed.metalgear

  • This is a valid way to use the driver using async
  • The try/catch failure is odd, I tried this:
var exeResult = session.ExecuteWriteAsync(async x =>
{
    var result = await x.RunAsync("BAD CYPHER");
    return await result.ToListAsync();
});

try
{
    await exeResult;
}
catch(ClientException ex)
{
    Console.WriteLine(ex);
}

And get the exception written to the output, so they are being raised, and caught - I guess this might be to do with what you're doing with the exception after you've caught it - i.e. are you logging/doing something with it?

  • For the multiple queries side of thing - they should be in separate sessions. Unless you're wanting it to all be in the same transaction - in which case you should be using the session.BeginTransactionAsync() methods. My guess is that the unpredictability is due to consumption of the response, and tidying up etc - new session instances should resolve that, and it's how the driver is intended to be used.

Thank you @charlotte.skardon. I am running multiple transactions with the same session like:

using var session = driver.AsyncSession(x => x.WithDefaultAccessMode(AccessMode.Write));

var q1Result = await session.ExecuteWriteAsync(async x =>
{
    var result = await x.RunAsync(GetQuery("query1"));
    return await result.ToListAsync();
});

var q2Result = await session.ExecuteWriteAsync(async x =>
{
    var result = await x.RunAsync(GetQuery("query2"));
    return await result.ToListAsync();
});

Are you suggesting I should not be reusing the session as this and instead create a separate session for each query?

what you're doing with the exception after you've caught it

I both log it to a log file and print it to a console. But when an exception is raised, it is neither added to the console nor the logs.

Hi, @hamed.metalgear

A couple of things for you to check out: Neo4j Driver Best Practices - Neo4j Graph Data Platform (in which David advises you to use one session per query).

Also, in the 5.5 drivers, we have released a new API (it's marked as experimental for now) which you can use to dramatically simplify how you interact with the driver: v5.5.0 - Feedback wanted on `ExecutableQuery` 📣 · Discussion #677 · neo4j/neo4j-dotnet-driver · GitHub

Yes, you should use a new session per transaction.

Sessions are resource cheap as they've already been instantiated - so you don't need to worry about that side of things.

I'm pretty confident that once you've gone down this route, a lot of the missing executions etc will resolve themselves, as I think you're not consuming your session results and that's giving you problems.

Well, there is a good use case for executing multiple queries in one session. This would occur if you need an entire operation to be atomic and you want to use the results of one query for the next query. These have to be done in the same transaction so you can roll back any changes if any issue arises during the sequence of queries.

Thank you all for your suggestions.