Project Awesome project awesome

neo4j-dotnet-driver

.Net driver for Neo4j (Bolt).

Package 244 stars GitHub

Neo4j .NET Driver

This repository contains the official Neo4j driver for .NET.

API Docs | Driver Manual | Change Log

For contribution guidance, see Contributing.

Installation

dotnet add package Neo4j.Driver

The package targets .NET 8, .NET 9, and .NET 10.

Neo4j.Driver is strong-named by default. Unlike previous major versions, there is no separate Neo4j.Driver.Signed package.

Additional packages

Neo4j.Driver.Reactive provides reactive (IObservable-based) APIs built on top of the core driver:

dotnet add package Neo4j.Driver.Reactive

Versioning

Driver upgrades within a major version will not contain breaking API changes, with the exception of the Neo4j.Driver.Preview namespace, which is reserved for feature previews.

Getting Started

URI schemes

Use bolt:// for a direct connection to a single server, or neo4j:// for a routing connection (required for clusters and recommended for Neo4j Aura). Append +s to require TLS (e.g. neo4j+s://), or +ssc to allow self-signed certificates.

Creating a driver

using Neo4j.Driver;

await using var driver = GraphDatabase.Driver(
    "neo4j+s://<dbid>.databases.neo4j.io",
    AuthTokens.Basic("neo4j", "<password>"));

IDriver maintains a connection pool internally. Create a single instance per application and share it across threads — IDriver is thread-safe. Sessions and transactions are not thread-safe and should not be shared across threads.

Verifying connectivity

await driver.VerifyConnectivityAsync();

Throws an exception if the server is unreachable or the credentials are invalid.

Always specify a database

var config = new QueryConfig(database: "neo4j");

Specifying the database avoids a server round-trip to determine the home database. Omitting it is only appropriate when working against a single-database deployment where the database name is genuinely unknown.

Querying

ExecutableQuery — single-query transactions

ExecutableQuery is the most concise API for running a single query in its own transaction. It handles retries and result materialisation automatically.

ExecuteAsync returns an EagerResult<IReadOnlyList<IRecord>> containing all records (Result), the returned column names (Keys), and a query summary (Summary). It can be destructured directly:

var (records, summary, keys) = await driver
    .ExecutableQuery("MATCH (n:Movie) RETURN n.title, n.released")
    .WithConfig(new QueryConfig(database: "neo4j"))
    .ExecuteAsync();

To map results directly to a type, chain AsObjectsAsync<T>():

record Movie(string title, int released);

var movies = await driver
    .ExecutableQuery("MATCH (n:Movie) RETURN n.title, n.released")
    .WithConfig(new QueryConfig(database: "neo4j"))
    .ExecuteAsync()
    .AsObjectsAsync<Movie>();

foreach (var movie in movies)
    Console.WriteLine(movie);

Managed transaction functions

For multi-query transactions, use ExecuteReadAsync or ExecuteWriteAsync on a session. The driver automatically retries the work on transient failures.

await using var session = driver.AsyncSession(o => o.WithDatabase("neo4j"));

// Read transaction
var names = await session.ExecuteReadAsync(async tx =>
{
    var cursor = await tx.RunAsync("MATCH (p:Person) RETURN p.name AS name");
    return await cursor.ToListAsync(r => r["name"].As<string>());
});

// Write transaction
await session.ExecuteWriteAsync(tx =>
    tx.RunAsync("CREATE (:Person {name: $name})", new { name = "Alice" }));

Manual transactions

Use BeginTransactionAsync when you need to co-ordinate a transaction with external work, such as committing only after a side-effect succeeds.

await using var session = driver.AsyncSession(o => o.WithDatabase("neo4j"));
await using var tx = await session.BeginTransactionAsync();
try
{
    await tx.RunAsync("CREATE (:Person {name: $name})", new { name = "Bob" });
    await tx.CommitAsync();
}
catch
{
    await tx.RollbackAsync();
    throw;
}

Types

Values in a record are exposed as object. Use the As<T>() extension method to convert to the expected type:

string name = record["name"].As<string>();
long count = record["count"].As<long>();

Cypher to .NET type mapping

Cypher Type .NET Driver Type
null null
List IList<object>
Map IDictionary<string, object>
Boolean bool
Integer long
Float double
String string
ByteArray byte[]
Point Point
Node INode
Relationship IRelationship
Path IPath

Temporal types

Cypher Type Driver Type Convertible CLR Types
Date LocalDate DateTime, DateOnly (.NET 6+)
Time OffsetTime TimeOnly (.NET 6+)
LocalTime LocalTime TimeSpan, DateTime
DateTime ZonedDateTime DateTimeOffset
LocalDateTime LocalDateTime DateTime
Duration Duration

Support

Back to Neo4j