Managed Transaction Workflows with .NET
This example demonstrates how to use the Neo4j .NET Driver to perform multi-step transactional work inside a managed write transaction. The code shows how to:
- Open an asynchronous session targeting a specific database
- Run one query to compute new aggregate values (average rating, total reviews)
- Run a second query to update the same book node using the computed aggregates
- Perform both operations within a single atomic transaction
- Safely extract results and handle typed values using the driver’s mapping helpers

Code sample
Below is the complete code, followed by a full walkthrough of reading data, computing aggregates, and writing updated values back to Neo4j in a single transaction.
await using var session = driver
.AsyncSession(conf => conf.WithDatabase("goodreads"));
await session.ExecuteWriteAsync(async tx =>
{
// Query 1: Compute fresh aggregates from reviews
var result1 = await tx
.RunAsync(readCypher, new { title = "The Tommyknockers" })
.ConfigureAwait(false);
var record = await result1.SingleAsync();
var avgRating = record["avgRating"].As<double>();
var ratingsCount = record["ratingsCount"].As<long>();
var bookId = record["bookId"].As<string>();
// Query 2: Update the book with recomputed values
var result2 = await tx
.RunAsync(writeCypher,
new { bookId, avgRating, ratingsCount })
.ConfiguerAwait(false);
var resultSummary = await result2.ConsumeAsync().ConfigureAwait(false);
})
.ConfigureAwait(false);Code language: C++ (cpp)
Updating ratings with the .NET Driver
1. Define the Cypher Queries
string readCypher = @"MATCH (b:Book {title:$title)<-[:WRITTEN_FOR]-(r:Review)
RETURN b.book_id AS bookId, avg(r.rating)
AS avgRating, count(r) AS ratingsCount";
string writeCypher = @"MATCH (b:Book {book_id: $bookId})
SET b.average_rating = $avgRating,
b.ratings_count = $ratingsCount
RETURN b"Code language: C# (cs)
Query 1 (readCypher):
- Finds a Book by title
- Retrieves all associated Review nodes
- Returns:
bookId(needed for the update step)- Computed
avgRating - Computed
ratingsCount
Query 2 (writeCypher):
- Locates a book by its ID
- Updates the node with:
- New
average_rating - New
ratings_count
- New
- Returns the updated book
Both queries are designed to run inside one transaction, so they operate on consistent data.
2. Open an Async Session
await using var session = driver
.AsyncSession(conf => conf.WithDatabase("goodreads"));Code language: C# (cs)
- Creates a lightweight, non-thread-safe session
- Targets the
goodreadsdatabase - Sessions are designed to be short-lived, used only for the duration of the work
3. Execute a Managed Write Transaction
await session.ExecuteWriteAsync(async tx =>
{
...
});Code language: C# (cs)
ExecuteWriteAsyncensures all contained operations run in a single write transaction- Neo4j will automatically retry the transaction if needed (e.g., transient errors)
4. Query 1: Compute Fresh Aggregates
var result1 = await tx
.RunAsync(readCypher, new { title = "The Tommyknockers" })
.ConfigureAwait(false);
var record = await result1.SingleAsync();
var avgRating = record["avgRating"].As<double>();
var ratingsCount = record["ratingsCount"].As<long>();
var bookId = record["bookId"].As<string>();Code language: C# (cs)
What’s happening:
RunAsyncexecutes the read query with a parameterized titleSingleAsync()retrieves the one expected row- Each value is extracted using type-safe
.As<T>()conversions
This provides you with the fresh values required for your update query.
5. Query 2: Update the Book Node
var result2 = await tx
.RunAsync(writeCypher,
new { bookId, avgRating, ratingsCount })
.ConfigureAwait(false);
var resultSummary = await result2
.ConsumeAsync()
.ConfigureAwait(false);Code language: C# (cs)
This step does the following:
- Runs the update query using the values computed in Query 1
- Updates the book’s
average_ratingandratings_count - Consumes the result so the driver can finalize query metadata and transaction state
Both queries complete successfully only if the entire transaction succeeds.
Summary
This example demonstrates how to:
- Manage a multi-step workflow inside a single, atomic transaction
- Compute derived values (aggregates) and write them back safely
- Use the Neo4j .NET Driver’s async session and transaction APIs
- Pass parameters securely and retrieve strongly-typed values


