Developer Center » Languages » C# » Code Guides » Managed Transaction Workflows with .NET

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
  • 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 goodreads database
  • 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)
  • ExecuteWriteAsync ensures 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:

  • RunAsync executes the read query with a parameterized title
  • SingleAsync() 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_rating and ratings_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

Share Article