Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions dotnet-manual/antora.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
name: dotnet-manual
title: Neo4j .NET Driver Manual
version: '5'
version: '6'
start_page: ROOT:index.adoc
nav:
- modules/ROOT/content-nav.adoc
asciidoc:
attributes:
dotnet-driver-version: '5.28' # the version of the package published
common-partial: 5@common-content:ROOT:partial$
common-image: 5@common-content:ROOT:image$
dotnet-driver-version: '6.0' # the version of the package published
common-partial: 6@common-content:ROOT:partial$
common-image: 6@common-content:ROOT:image$
41 changes: 17 additions & 24 deletions dotnet-manual/modules/ROOT/pages/bookmarks.adoc
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
= Coordinate parallel transactions

When working with a Neo4j cluster, <<causal_consistency>> is enforced by default in most cases, which guarantees that a query is able to read changes made by previous queries.
When working with a Neo4j cluster, <<causal_consistency>> is enforced by default in most cases, guaranteeing that a query is able to read changes made by previous queries.
The same does not happen by default for multiple xref:transactions.adoc[transactions] running in parallel though.
In that case, you can use _bookmarks_ to have one transaction wait for the result of another to be propagated across the cluster before running its own work.
This is not a requirement, and *you should only use bookmarks if you _need_ casual consistency across different transactions*, as waiting for bookmarks can have a negative performance impact.

A _bookmark_ is a token that represents some state of the database.
By passing one or multiple bookmarks along with a query, the server will make sure that the query does not get executed before the represented state(s) have been established.
This is not a requirement, and *you should only use <<bookmark,bookmarks>> if you _need_ casual consistency across different transactions*, as waiting for bookmarks can have a negative performance impact.


[#executablequery]
Expand Down Expand Up @@ -38,7 +36,7 @@ await driver.ExecutableQuery("<QUERY>")
[#single-session]
== Bookmarks within a single session

Bookmark management happens automatically for queries run within a single session, so you can trust that queries inside one session are causally chained.
Bookmark management happens automatically for queries run within a single session: queries inside the same session are causally chained.

[source, csharp]
----
Expand Down Expand Up @@ -131,44 +129,39 @@ async Task printFriendships(IAsyncQueryRunner tx) {
}
----

<1> Collect and combine bookmarks from different sessions using `AsyncSession.LastBookmarks`, storing them in a link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.Bookmarks.html[`Bookmarks`] object.
<1> Collect and combine bookmarks from different sessions using `AsyncSession.LastBookmarks`, storing them in a link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.Bookmarks.html[`Bookmarks`] object.
<2> Use them to initialize another session with the `.WithBookmarks()` config method.

image:{common-image}/driver-passing-bookmarks.svg[]

[TIP]
The use of bookmarks can negatively impact performance, since all queries are forced to wait for the latest changes to be propagated across the cluster.
For simple use-cases, try to group queries within a single transaction, or within a single session.
If possible, group queries within a single transaction, or within a single session.

////
Note: not possible on .NET

== Mix `.ExecutableQuery()` and sessions

To ensure causal consistency among transactions executed partly with `.ExecutableQuery()` and partly with sessions, you can retrieve the default bookmark manager for `ExecutableQuery` instances through `driver.executableQueryBookmarkManager()` and pass it to new sessions through the `.WithBookmarkManager()` config method.
To ensure causal consistency among transactions executed partly with `.ExecutableQuery()` and partly with sessions, you can retrieve the default bookmark manager for the `ExecutableQuery` interface through `driver.GetExecutableQueryBookmarkManager()` and pass it to new sessions through the link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.SessionConfigBuilder.WithBookmarkManager.html#Neo4j_Driver_SessionConfigBuilder_WithBookmarkManager_Neo4j_Driver_IBookmarkManager_[`SessionConfigBuilder.WithBookmarkManager()`] config method.
This will ensure that all work is executed under the same bookmark manager and thus causally consistent.

[source, java]
[source, dotnet]
----
// import org.neo4j.driver.Driver;
// import org.neo4j.driver.SessionConfig;

driver.executableQuery("<QUERY 1>").execute();
await driver.ExecutableQuery("<QUERY 1>").ExecuteAsync();

try (var session = driver.session(SessionConfig.builder()
.withBookmarkManager(driver.executableQueryBookmarkManager())
.build())) {
using var session = driver.AsyncSession(conf => conf.
WithDatabase("<database-name>")
.WithBookmarkManager(driver.GetExecutableQueryBookmarkManager()));

// every query inside this session will be causally chained
// (i.e., can read what was written by <QUERY 1>)
await session.ExecuteWriteAsync(async tx => await tx.RunAsync("<QUERY 2>"));

// every query inside this session will be causally chained
// (i.e., can read what was written by <QUERY 1>)
session.executeWriteWithoutResult(tx -> tx.run("<QUERY 2>"));
}

// subsequent executableQuery calls will also be causally chained
// subsequent ExecutableQuery calls will also be causally chained
// (i.e., can read what was written by <QUERY 2>)
driver.executableQuery("<QUERY 3>").execute();
await driver.ExecutableQuery("<QUERY 3>").ExecuteAsync();
----
////


ifndef::backend-pdf[]
Expand Down
18 changes: 8 additions & 10 deletions dotnet-manual/modules/ROOT/pages/connect-advanced.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ include::{common-partial}/connect-advanced.adoc[tag=connection-protocols]
=== Basic authentication (default)

The basic authentication scheme relies on traditional username and password.
These can either be the credentials for your local installation, or the ones provided with an Aura instance.

[source, csharp]
----
Expand Down Expand Up @@ -49,7 +48,7 @@ await using var driver = GraphDatabase.Driver(dbUri, AuthTokens.Bearer(ticket));
[#auth-custom]
=== Custom authentication

Use link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.AuthTokens.Custom.html[`AuthTokens.Custom()`] to log into a server having a custom authentication scheme.
Use link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.AuthTokens.Custom.html[`AuthTokens.Custom()`] to log into a server having a custom authentication scheme.

[source, csharp, test-skip]
----
Expand All @@ -68,13 +67,12 @@ If authentication is disabled on the server, the authentication parameter can be


[#rotating-tokens]
[role=label--new-5.14]
== Rotating authentication tokens

It is possible to rotate authentication tokens that are expected to expire (e.g. SSO).
You need to provide an link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.IAuthTokenManager.html[`IAuthTokenManager`] instance when instantiating the `IDriver`, rather than a static authentication token.
You need to provide an link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.IAuthTokenManager.html[`IAuthTokenManager`] instance when instantiating the `IDriver`, rather than a static authentication token.

The easiest way to get started is to use link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.AuthTokenManagers.html[one of built-in `AuthTokenManagers` implementations]. AuthTokenManagers work with link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.AuthTokenAndExpiration.html[`AuthTokenAndExpiration`] objects.
The easiest way to get started is to use link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.AuthTokenManagers.html[one of built-in `AuthTokenManagers` implementations]. AuthTokenManagers work with link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.AuthTokenAndExpiration.html[`AuthTokenAndExpiration`] objects.

.Rotating a bearer token expiring every 60 seconds
[source, csharp, test-skip]
Expand Down Expand Up @@ -110,7 +108,7 @@ You can switch users at both xref:query-simple.adoc#impersonation[query level] a


[#mtls]
[role=label--new-5.27 label--not-on-aura]
[role=label--not-on-aura]
== Mutual TLS (client-side certificates as 2FA)

Mutual TLS (mTLS) allows you to use a client certificate as second factor for authenticating with the server.
Expand All @@ -121,7 +119,7 @@ The client's certificate and public key must be placed in the server's `<NEO4J_H
[NOTE]
For mTLS to work, the driver's connection with the server must be encrypted, i.e. the xref:_connection_protocols_and_security[connection URI scheme] must be either `+s` or `+ssc` (ex. `neo4j+s://example.com:7687`).

Use the config method link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.ConfigBuilder.WithClientCertificateProvider.html[`WithClientCertificateProvider()`] to provide an link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.IClientCertificateProvider.html[`IClientCertificateProvider`]. +
Use the config method link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.ConfigBuilder.WithClientCertificateProvider.html[`WithClientCertificateProvider()`] to provide an link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.IClientCertificateProvider.html[`IClientCertificateProvider`]. +
Your implementation must provide a thread-safe `GetCertificateAsync()` method, returning a `ValueTask<link:https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2?view=net-9.0[X509Certificate2]>`.

[source, csharp, test-skip]
Expand Down Expand Up @@ -152,7 +150,7 @@ await driver.VerifyConnectivityAsync();
[#logging]
== Logging

To enable logging use link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.ConfigBuilder.WithLogger.html[`.withLogger(ILogger)`] config method when creating a `Driver` object, providing an implementation of link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.ILogger.html[`ILogger`].
To enable logging use link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.ConfigBuilder.WithLogger.html[`.withLogger(ILogger)`] config method when creating a `Driver` object, providing an implementation of link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.ILogger.html[`ILogger`].

.Enable debug logging with example implementation of `ILogger`
[source, csharp]
Expand Down Expand Up @@ -251,7 +249,7 @@ public class Neo4jLogger : ILogger {
When creating an `IDriver` object, you can specify a _resolver_ function to resolve the connection address the driver is initialized with.
Note that addresses that the driver receives in routing tables are not resolved with the custom resolver.

You specify a resolver through the link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.ConfigBuilder.WithResolver.html)[`.WithResolver()`] config method, which works with link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.IServerAddressResolver.html[`IServerAddressResolver`] objects.
You specify a resolver through the link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.ConfigBuilder.WithResolver.html)[`.WithResolver()`] config method, which works with link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.IServerAddressResolver.html[`IServerAddressResolver`] objects.

*The resolved address must have the same port and URI scheme.*
In the example below, the driver is initialized with a `neo4j://` URI scheme and port `7687`, so the resolved address provided in `dbUri` must also be available at `neo4j://` and port `7687`.
Expand Down Expand Up @@ -291,7 +289,7 @@ class ListAddressResolver : IServerAddressResolver {

== Further connection parameters

You can find all `IDriver` configuration parameters in the link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.ConfigBuilder.html[API documentation -> ConfigBuilder].
You can find all `IDriver` configuration parameters in the link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.ConfigBuilder.html[API documentation -> ConfigBuilder].


ifndef::backend-pdf[]
Expand Down
8 changes: 4 additions & 4 deletions dotnet-manual/modules/ROOT/pages/connect.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ Console.WriteLine("Connection established.");
----

<1> Creating a `IDriver` instance only provides information on _how_ to access the database, but does not actually _establish_ a connection.
Connection is instead deferred to when the first query is executed.
Connection is deferred to when the first query is executed.
<2> To verify immediately that the driver can connect to the database (valid credentials, compatible versions, etc), use the `.VerifyConnectivityAsync()` method after initializing the driver.

Both the creation of a `IDriver` object and the connection verification can raise a number of different link:https://neo4j.com/docs/api/dotnet-driver/{dotnet-driver-version}/api/Neo4j.Driver.Neo4jException.html[exceptions].
Both the creation of a `IDriver` object and the connection verification can raise a number of different link:https://neo4j.com/docs/api/dotnet-driver/current/api/Neo4j.Driver.Neo4jException.html[exceptions].
Since a connection error is a blocker for any subsequent task, the most common choice is to let the program crash should an exception occur while establishing a connection.

**`IDriver` objects are immutable, thread-safe, and expensive to create**, so your application should create only one instance and pass it around (you may share `IDriver` instances across threads).
**`IDriver` objects are immutable, thread-safe, and expensive to create**, so your application should create only one instance and pass it around (you can share `IDriver` instances across threads).
You can xref:query-simple#impersonation[run queries through several different users] without creating a new `IDriver` instance.
If you want to alter a `IDriver` configuration, you need to create a new object.

Expand All @@ -39,7 +39,7 @@ The driver also supports other xref:connect-advanced.adoc#authentication-methods
[#aura]
== Connect to an Aura instance

When you create an <<Aura>> instance, you receive a text file (a so-called _Dotenv file_) containing the connection information to the database in the form of environment variables.
When you create an <<Aura>> instance, you receive a text file (a so-called _Dotenv file_) containing the connection information to the database as environment variables.
The file has a name of the form `Neo4j-a0a2fa1d-Created-2023-11-06.txt`.

You can either manually extract the URI and the credentials from that file, or use a third party-module to load them.
Expand Down
Loading
Loading