neo4j-driver-dotnet-skill
Neo4j .NET Driver v6 — IDriver lifecycle, DI registration (singleton), ExecutableQuery fluent API, ExecuteReadAsync/ExecuteWriteAsync managed transactions, IResultCursor (FetchAsync/ ToListAsync), record value access (.Get<T>/As<T>), null safety, UNWIND batching, temporal types, await using, EagerRe
By neo4j-contrib · 489 installs
npx skills add neo4j-contrib/neo4j-skills --skill neo4j-driver-dotnet-skill
Source repository · Upstream listing
When to Use
Writing C or .NET code connecting to Neo4j
Setting up IDriver , DI registration, or session/transaction lifecycle
Questions about ExecutableQuery , IResultCursor , async patterns, result mapping
Debugging sessions, type mapping, null safety, or error handling in .NET
When NOT to Use
Writing/optimizing Cypher queries → neo4j cypher skill
Upgrading from older driver version → neo4j migration skill
Install
Package Use
Neo4j.Driver Async API — use this
Neo4j.Driver.Simple Synchronous wrapper
Neo4j.Driver.Reactive System.Reactive streams
Driver Lifecycle
IDriver — thread safe, connection pooled, expensive to create. Create one per application.
IDriver and IAsyncSession implement IAsyncDisposable — always await using , never plain using .
Auth options: AuthTokens.Basic(u, p) / AuthTokens.Bearer(token) / AuthTokens.Kerberos(ticket) / AuthTokens.None
Environment Variables
Load connection config from environment / appsettings.json — never hardcode credentials.
Override with environment variables (standard .NET behavior): Neo4j Uri=neo4j+s://... (double underscore = colon separator). Never commit appsettings.json with real credentials — use appsettings.Development.json (gitignored) or env vars in CI/production.
DI Registration (ASP.NET Core)
Register IDriver as singleton — never Scoped or Transient. Never register IAsyncSession in DI.
Choose the Right API
API When Auto retry Streaming
driver.ExecutableQuery() Most queries — simple default ✅ ❌ eager
session.ExecuteReadAsync/WriteAsync() Large results, multi query tx ✅ ✅
session.RunAsync() LOAD CSV , CALL {} IN TRANSACTIONS ❌ ✅
session.BeginTransactionAsync() Multi function, external coordination ❌ ✅
ExecutableQuery — Recommended Default
Fluent builder; manages session, transaction, retries, and bookmarks automatically.
Never await omitted: ExecuteAsync() returns Task — missing await compiles silently but query never runs.
Never string interpolate Cypher. Always WithParameters() — prevents injection, enables plan caching.
Managed Transactions
Use for large result sets (lazy streaming) or multiple queries per transaction. Callback auto retried on transient failure — keep it idempotent, no side effects inside.
Cursor rules:
Consume with ToListAsync() or FetchAsync() loop inside the callback
Returning a cursor from the callback → transaction closes → cursor invalid → exception
Async void trap:
FetchAsync Loop
Cursor consumption methods:
Method Records Summary Use
ToListAsync() ✅ all ❌ Need records
ToListAsync(mapper) ✅ mapped ❌ Need mapped records
FetchAsync() loop ✅ one/time ❌ until ConsumeAsync Large/lazy
ConsumeAsync() ❌ discards ✅ Need counters
IResultSummary profiling [v6.3.0+]: summary.Profile is [Obsolete] → use summary.QueryProfile ( IQueryProfile ), whose DbHits , Rows , Time , PageCacheHits , PageCacheMisses are long? — null means not recorded, distinct from 0 . Gate on summary.HasProfile .
SingleAsync() ✅ exactly 1 ❌ Expect one row
Record Value Access
Type Mapping
Cypher .NET default Notes
Integer long safe: int , long? , int?
Float double safe: float , double?
String string use string? if nullable
Boolean bool
List IReadOnlyList<object
Map IReadOnlyDictionary<string,object
Node INode .Labels , .Properties , .ElementId
Relationship IRelationship .Type , .StartNodeElementId
Date LocalDate .ToDateOnly() (.NET 6+)
DateTime ZonedDateTime .ToDateTimeOffset() (ms precision)
LocalDateTime LocalDateTime
Duration Duration .ToTimeSpan() throws if has months/days
null null use nullable types
ElementId stable within one transaction only — do not use to MATCH across separate transactions.
UNWIND Batching
Custom class instances passed to WithParameters for UNWIND do not serialize — use new object[] { new { ... } } or Dictionary<string, object .
Object Mapping (Preview API)
Error Handling
Catch ClientException before Neo4jException — it's a subclass; generic handler swallows it.
ex.GqlStatus — stable GQL status codes; prefer over string matching ex.Code .
Explicit transaction rollback can itself throw — isolate it:
If CommitAsync() throws a network error, commit may or may not have succeeded — design writes idempotent with MERGE + unique constraints.
Common Mistakes
Mistake Fix
using var driver await using var driver — IDriver is IAsyncDisposable
using var session await using var session
IDriver as Scoped/Transient in DI Register as Singleton
IAsyncSession in DI Never — open per unit of work
Missing await on ExecuteAsync() Task silently never runs
async tx = tx.RunAsync(...) no inner await Remove async , return Task directly
Omit database in QueryConfig / AsyncSession Always specify — saves a round trip
No CancellationToken in web apps Propagate HttpContext.RequestAborted
.As<string () on null graph value .As<string? () — non nullable throws
record["key"] absent key Check record.Keys.Contains() first
cursor.Current after FetchAsync loop Last record, not null — don't use after loop
FetchAsync() after false return Throws — stop loop, don't call again
Return cursor from managed tx callback Consume with ToListAsync() inside callback
Need counters from session write await cursor.ConsumeAsync()
AsObject<T () CS1061 compile error Add using Neo4j.Driver.Preview.Mapping;
ResultAvailableAfter for total timing Use ResultConsumedAfter (full wall clock)
Custom class in WithParameters for UNWIND Use anonymous types or Dictionary<string,object
Rename C param but not Cypher $param Anonymous property names must match $param names
ExecuteWriteAsync for reads Use ExecuteReadAsync — routes to replicas
Side effects inside managed tx callback Move outside — callback retried on failure
Duration.ToTimeSpan() with months/days Only safe for pure second/nanosecond durations
Catch Neo4jException before ClientException ClientException is subclass — catch it first
References
Load on demand:
[references/transactions.md](references/transactions.md) — explicit transactions, BeginTransactionAsync , rollback, commit uncertainty, TransactionConfig (timeout, metadata), causal consistency and bookmarks
[references/performance.md](references/performance.md) — spatial types (Point/WGS 84/Cartesian), connection pool tuning, WithFetchSize , session config options, CancellationToken patterns, large result streaming
[references/object mapping.md](references/object mapping.md) — AsObject<T , blueprint mapping, lambda mapping, AsObjectsAsync<T , repository pattern example
Checklist
[ ] IDriver registered as singleton in DI (or await using for short lived apps)
[ ] await using on driver and sessions (not plain using )
[ ] database specified in QueryConfig / AsyncSession config
[ ] ExecutableQuery used for simple queries; ExecuteReadAsync / ExecuteWriteAsync for streaming/multi query
[ ] Cursor consumed inside managed tx callback (not returned)
[ ] Nullable types ( string? , int? ) on any graph value that can be null
[ ] WithParameters() used (no string interpolation)
[ ] UNWIND batching with anonymous types (not custom class instances)
[ ] CancellationToken propagated in web app handlers
[ ] ClientException caught before Neo4jException
[ ] Writes idempotent ( MERGE + constraints) for retry safety
[ ] No side effects inside ExecuteReadAsync / ExecuteWriteAsync callbacks