Neo4j Bolt driver for .NET
This is the official Neo4j driver for .NET.
Resources to get you started:
This section is prepared for application developers who would like to use this driver in application projects for connecting to a Neo4j instance or a Neo4j cluster.
For users who wish to migrate from 1.7 series to 4.0, checkout our migration guide.
Starting with 5.0, the Neo4j Drivers will be moving to a monthly release cadence. A minor version will be released on the last Friday of each month so as to maintain versioning consistency with the core product (Neo4j DBMS) which has also moved to a monthly cadence.
As a policy, patch versions will not be released except on rare occasions. Bug fixes and updates will go into the latest minor version and users should upgrade to that. Driver upgrades within a major version will never contain breaking API changes(Excluding the Neo4j.Driver.Preview namespace).
See also: https://neo4j.com/developer/kb/neo4j-supported-versions/
The Neo4j driver is distributed under three packages:
Add the asynchronous driver to your project using the Nuget Package Manager:
PM> Install-Package Neo4j.Driver
There is also a strong named version of each driver package available on Nuget such as Neo4j.Driver.Signed. Both packages contain the same version of the driver, only the latter is strong named. Consider using the strong named version only if your project is strong named and/or you are forced to use strong named dependencies.
Add the strong named version of the asynchronous driver to your project using the Nuget Package Manager:
PM> Install-Package Neo4j.Driver.Signed
Connect to a Neo4j database
using var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "password"));
var queryOperation = await driver.ExecutableQuery("CREATE (n) RETURN n").ExecuteAsync();
There are a few points that need to be highlighted when adding this driver into your project:
IDriver
instance maintains a pool of connections inside; as a result, it is recommended that you use only one driver per application.The result of executing a query in the way shown above is an EagerResult<T>
, where T
is the type of data returned from the query. In the simplest case, this will be an IReadOnlyList<IRecord>
, which is a fully materialized list of the records returned by the query. Other types of results can be used by using the fluent API exposed by the IExecutableQuery
type.
var queryOp = await driver
.ExecutableQuery("MATCH (person:Person) RETURN person")
.WithMap(r => r["person"].As<INode>()["name"].As<string>())
.ExecuteAsync();
// queryOp.Result is IReadOnlyList<string>
var queryOp = await driver
.ExecutableQuery("MATCH (person:Person) RETURN person")
.WithMap(r => r["person"].As<INode>()["name"].As<string>())
.WithFilter(s => s.StartsWith("A"))
.ExecuteAsync();
// queryOp.Result is IReadOnlyList<string> - note that this type of filtering
// is done on the client and is not a substitute for filtering in the Cypher
var queryOp = await driver
.ExecutableQuery("MATCH (person:Person) RETURN person")
.WithMap(r => r["person"].As<INode>()["age"].As<int>())
.WithReduce(() => 0, (r, n) => r + n)
.ExecuteAsync();
// queryOp.Result is `int`, the sum of all the ages
var queryOp = await driver
.ExecutableQuery("MATCH (person:Person) RETURN person")
.WithStreamProcessor(
async stream =>
{
double total = 0;
int count = 0;
await foreach(var record in stream)
{
var ages = record["person"].As<INode>()["age"].As<string>();
total += age;
count++;
}
return total / count;
})
.ExecuteAsync();
// queryOp.Result is `double`, the average of all the ages
A cypher execution result is comprised of a stream records followed by a result summary. The stream can be accessed directly by using the WithStreamProcessor
method as shown above. The stream implements IAsyncEnumerable<IRecord>
and as such can be accessed with normal Linq methods by adding the System.Linq.Async
package to your project. The ExecuteAsync
method will return an EagerResult<T>
, where T
is the type of value returned from the method passed to WithStreamProcessor
. This value will be stored in the Result
property of the EagerResult
, with the Summary
and Keys
property containing further information about the execution of the query.
Values in a record are currently exposed as of object
type.
The underlying types of these values are determined by their Cypher types.
The mapping between driver types and Cypher types are listed in the table bellow:
Cypher Type | Driver Type |
---|---|
null | null |
List | IList< object > |
Map | IDictionary<string, object> |
Boolean | boolean |
Integer | long |
Float | float |
String | string |
ByteArray | byte[] |
Point | Point |
Node | INode |
Relationship | IRelationship |
Path | IPath |
To convert from object
to the driver type, a helper method ValueExtensions#As<T>
can be used:
IRecord record = await result.SingleAsync();
string name = record["name"].As<string>();
The new temporal types in Neo4j 3.4 series are introduced with the 1.6 series of the driver. Considering the nanosecond precision and large range of supported values, all temporal types are backed by custom types at the driver level.
The mapping among the Cypher temporal types, driver types, and convertible CLR temporal types - DateTime, TimeSpan and
DateTimeOffset - (via IConvertible
interface) are as follows:
Cypher Type | Driver Type | Convertible CLR Type |
---|---|---|
Date | LocalDate | DateTime |
Time | OffsetTime | --- |
LocalTime | LocalTime | TimeSpan, DateTime |
DateTime | ZonedDateTime | DateTimeOffset |
LocalDateTime | LocalDateTime | DateTime |
Duration | Duration | --- |
Receiving a temporal value as driver type:
IRecord record = await result.SingleAsync();
ZonedDateTime datetime = record["datetime"].As<ZonedDateTime>();
Converting a temporal value to the CLR type:
object record = await result.SingleAsync()["datetime"];
DateTimeOffset datetime = record["datetime"].As<DateTimeOffset>();
// which is equivalent to
// ZonedDateTime cyDatetime = record["datetime"].As<ZonedDateTime>();
// DateTimeOffset datetime = cyDatetime.ToDateTimeOffset();
Note:
ValueOverflowException
is thrown
when the conversion is not possible.Date
) provide nanosecond precision. However CLR types only support ticks (100
nanosecond) precision.
So a temporal type created via Cypher might not be convertible to the CLR type (a ValueTruncationException
is thrown
when a conversion is requested in this case).ZonedDateTime
represents date and times with either offset or time zone information. Time zone names adhere to
the IANA system,
rather than
the Windows system.
Although there is no support for inbound
time zone name conversions, a conversion from IANA system to Windows system may be necessary if a conversion
to DateTimeOffset
or an access to Offset
is
requested by the
user. Unicode CLDR mapping
is used for this conversion. Please bear in mind that Windows time zone database do not provide precise historical
data, so you may end up with inaccurate
DateTimeOffset
values for past values. It is recommended that you use driver level temporal types to avoid these
inaccuracies.
The driver accepts a logger that implements ILogger
interface.
To pass a ILogger
to this driver:
IDriver driver = GraphDatabase.Driver("neo4j://localhost:7687", AuthTokens.Basic("username", "pasSW0rd"), o => o.WithLogger(logger));
In this ILogger
interface, each logging method takes a message format string and message arguments as input.
The full log messages can be restored using string.format(message_format, message_argument)
.
An example of implementing ILogger
with Microsoft.Extensions.Logging
:
public class DriverLogger : ILogger
{
private readonly Microsoft.Extensions.Logging.ILogger _delegator;
public DriverLogger(Microsoft.Extensions.Logging.ILogger delegator)
{
_delegator = delegator;
}
public void Error(Exception cause, string format, params object[] args)
{
_delegator.LogError(default(EventId), cause, format, args);
}
...
}
This section is for users who would like to migrate their 1.7 driver to 4.0. The following sections serve as a quick look at what have been added and/or changed in 4.0 .NET driver. For more information, also checkout our Driver Migration Guide.
Neo4j.Driver.Reactive
when using together with Neo4j 4.0 databases.SessionConfig#ForDatabase
.IDriver#SupportsMultiDbAsync
is added for querying if the remote database supports
multi-databases.IDriver#VerifyConnectivityAsync
method is introduced for verify the availability of remote DBMS.Neo4j.Driver
instead of the
old Neo4j.Driver.V1
.Neo4j.Driver
package contains only the asynchronous API. Synchronous session API has been moved to the
namespace Neo4j.Driver.Simple
.neo4j
scheme is added and designed to work with all possible 4.0 server deployments. bolt
scheme is still
available for explicit direct connections with a single instance and/or a single member in a cluster. For 3.x
servers, neo4j
replaces bolt+routing
.IAsync
, whereas synchronous methods
are kept under the old interface but live in package Neo4j.Driver.Simple
. This change ensures that blocking and
no-blocking APIs can never be mixed together.IDriver#Session
methods now make use of a session option builder rather than method arguments.string
and/or a list of strings to a Bookmark
object.ITransaction#Success
is replaced with ITransaction#Commit
.
However unlike ITransaction#Success
which only marks the transaction to be successful and then waits
for ITransaction#Dispose
to actually perform the real commit, ITransaction#Commit
commits the transaction
immediately.
Similarly, ITransaction#Failure
is replaced with ITransaction#Rollback
. A transaction in 4.0 can only be committed
OR rolled back once.
If a transaction is not committed explicitly using ITransaction#Commit
, ITransaction#Dispose
will roll back the
transaction.Statement
has been renamed to Query
. IStatementResult
has been simplified to IResult
.
Similarly, IStatementResultCursor
has been renamed to IResultCursor
.IResult#Consume
and/or the outer scope where the result is created, such as a transaction or a session, has
been closed.
Attempts to access consumed results will be responded with a ResultConsumedException
.LoadBalancingStrategy
is removed from Config
class and the drivers always default to LeastConnectedStrategy
.IDriverLogger
has been renamed to ILogger
.TrustStrategy
is replaced with TrustManager
.This section targets at people who would like to compile the source code on their own machines for the purpose of, for example, contributing a PR to this repository. Before contributing to this project, please take a few minutes and read our Contributing Criteria.
Snapshot builds are available at our MyGet feed, add the feed to your Nuget Sources to access snapshot artifacts.
Tests require the latest Testkit 4.3, Python3 and Docker.
Testkit is needed to be cloned and configured to run against the Dotnet Driver. Use the following steps to configure Testkit.
git clone https://github.com/neo4j-drivers/testkit.git
pip3 install -r requirements.txt
export TEST_DRIVER_NAME=dotnet
export TEST_DRIVER_REPO=<path for the root folder of driver repository>
To run test against against some Neo4j version:
python3 main.py
More details about how to use Teskit could be found on its repository
The driver is written in C# 7 so will require Visual Studio 2017.
The integration tests use boltkit to download and install a database instance on your local machine. They can fail for three main reasons:
The database installation uses boltkit neoctrl-install
command to install the database.
It is possible to run the integration tests against a specific version by setting environment variable NEOCTRL_ARGS
.
The simplest way to run all tests from command line is to run runTests.ps1
powershell script:
.\Neo4j.Driver\runTests.ps1
Any parameter to this powershell script will be used to reset environment variable NEOCTRL_ARGS
:
.\Neo4j.Driver\runTests.ps1 -e 4.0.0
The driver targets at .NET Standard 2.0. and .NET 5.0
As a result, it can be compiled and run on linux machines after installing for example .NET Core 2.0 library.
As for IDE, we recommend Rider for daily development.
The integration tests require boltkit to be installed and accessible via
command line.
If any problem to start a Neo4j Server on your machine, you can start the test Bolt Server yourself at localhost:7687
and then set environment variable DOTNET_DRIVER_USING_LOCAL_SERVER=true