The session API
This section details the Session API that is made available by the driver.
Simple sessions
Simple sessions provide a "classic" blocking style API for Cypher execution. In general, simple sessions provide the easiest programming style to work with since API calls are executed in a strictly sequential fashion.
Lifecycle
The session lifetime extends from session construction to session closure. In languages that support them, simple sessions are usually scoped within a context block; this ensures that they are properly closed and that any underlying connections are released and not leaked.
using (var session = driver.Session(...)) {
// transactions go here
}
Sessions can be configured in a number of different ways. This is carried out by supplying configuration inside the session constructor. See Session configuration for more details.
Transaction functions
Transaction functions are used for containing transactional units of work. This form of transaction requires minimal boilerplate code and allows for a clear separation of database queries and application logic.
Transaction functions are also desirable since they encapsulate retry logic and allow for the greatest degree of flexibility when swapping out a single instance of server for a cluster.
Transaction functions can be called as either read or write operations. This choice will route the transaction to an appropriate server within a clustered environment. If you are operating in a single instance environment, this routing has no impact. It does give you flexibility if you choose to adopt a clustered environment later on.
Before writing a transaction function it is important to ensure that it is designed to be idempotent. This is because a function may be executed multiple times if initial runs fail.
Any query results obtained within a transaction function should be consumed within that function, as connection-bound resources cannot be managed correctly when out of scope. To that end, transaction functions can return values but these should be derived values rather than raw results.
Transaction functions are the recommended form for containing transactional units of work. When a transaction fails, the driver retry logic is invoked. For several failure cases, the transaction can be immediately retried against a different server. These cases include connection issues, server role changes (e.g. leadership elections) and transient errors. |
The methods ExecuteRead and ExecuteWrite have replaced ReadTransaction and WriteTransaction , which are deprecated in version 5.x and will be removed in version 6.0.
|
public void AddPerson(string name)
{
using var session = Driver.Session();
session.ExecuteWrite(tx => tx.Run("CREATE (a:Person {name: $name})", new { name }).Consume());
}
Auto-commit transactions (or implicit transactions)
An auto-commit transaction, or implicit transaction, is a basic but limited form of transaction. Such a transaction consists of only one Cypher query and is not automatically retried on failure. Therefore, any error scenarios will need to be handled by the client application itself.
Auto-commit transactions serve the following purposes:
-
simple use cases such as when learning Cypher or writing one-off scripts.
-
operations such as batched data load operations, where the driver cannot be aware of the committed state and therefore cannot safely request a retry. The operator will have to perform a
retry
orundo
under these circumstances.
The driver does not retry auto-commit queries on failure since it is unaware of what state has been committed at the point of failure. |
Unlike other kinds of Cypher query, Please refer to the following for more details: |
public void AddPerson(string name)
{
using var session = Driver.Session();
session.Run("CREATE (a:Person {name: $name})", new { name });
}
Consuming results
Query results are typically consumed as a stream of records. The drivers provide a way to iterate through that stream.
public List<string> GetPeople()
{
using var session = Driver.Session();
return session.ExecuteRead(
tx =>
{
var result = tx.Run("MATCH (a:Person) RETURN a.name ORDER BY a.name");
return result.Select(record => record[0].As<string>()).ToList();
});
}
Retaining results
Within a session, only one result stream can be active at any one time. Therefore, if the result of one query is not fully consumed before another query is executed, the remainder of the first result will be automatically buffered within the result object.
This buffer provides a staging point for results, and divides result handling into fetching (moving from the network to the buffer) and consuming (moving from the buffer to the application).
For large results, the result buffer may require a significant amount of memory. For this reason, it is recommended to consume results in order wherever possible. |
Client applications can choose to take control of more advanced query patterns by explicitly retaining results. Such explicit retention may also be useful when a result needs to be saved for future processing. The drivers offer support for this process, as per the example below:
public int AddEmployees(string companyName)
{
using var session = Driver.Session();
var persons = session.ExecuteRead(tx => tx.Run("MATCH (a:Person) RETURN a.name AS name").ToList());
return persons.Sum(
person => session.ExecuteWrite(
tx =>
{
var result = tx.Run(
"MATCH (emp:Person {name: $person_name}) " +
"MERGE (com:Company {name: $company_name}) " +
"MERGE (emp)-[:WORKS_FOR]->(com)",
new { person_name = person["name"].As<string>(), company_name = companyName });
result.Consume();
return 1;
}));
}
Asynchronous sessions
Asynchronous sessions provide an API wherein function calls typically return available objects such as futures. This allows client applications to work within asynchronous frameworks and take advantage of cooperative multitasking.
Lifecycle
Session lifetime begins with session construction. A session then exists until it is closed, which is typically set to occur after its contained query results have been consumed.
Sessions can be configured in a number of different ways. This is carried out by supplying configuration inside the session constructor.
See Session configuration for more details.
Transaction functions
Transaction functions are the recommended form for containing transactional units of work. This form of transaction requires minimal boilerplate code and allows for a clear separation of database queries and application logic. Transaction functions are also desirable since they encapsulate retry logic and allow for the greatest degree of flexibility when swapping out a single instance of server for a cluster.
Functions can be called as either read or write operations. This choice will route the transaction to an appropriate server within a clustered environment. If you are in a single instance environment, this routing has no impact but it does give you the flexibility should you choose to later adopt a clustered environment.
Before writing a transaction function it is important to ensure that any side-effects carried out by a transaction function should be designed to be idempotent. This is because a function may be executed multiple times if initial runs fail.
Any query results obtained within a transaction function should be consumed within that function, as connection-bound resources cannot be managed correctly when out of scope. To that end, transaction functions can return values but these should be derived values rather than raw results.
When a transaction fails, the driver retry logic is invoked. For several failure cases, the transaction can be immediately retried against a different server. These cases include connection issues, server role changes (e.g. leadership elections) and transient errors. Retry logic can be configured when creating a session. |
The methods ExecuteReadAsync and ExecuteWriteAsync have replaced ReadTransactionAsync and WriteTransactionAsync , which are deprecated in version 5.x and will be removed in version 6.0.
|
public async Task<List<string>> PrintAllProducts()
{
await using var session = Driver.AsyncSession();
// Wrap whole operation into an managed transaction and
// get the results back.
return await session.ExecuteReadAsync(
async tx =>
{
var products = new List<string>();
// Send cypher query to the database
var reader = await tx.RunAsync(
"MATCH (p:Product) WHERE p.id = $id RETURN p.title", // Cypher query
new { id = 0 } // Parameters in the query, if any
);
// Loop through the records asynchronously
while (await reader.FetchAsync())
// Each current read in buffer can be reached via Current
{
products.Add(reader.Current[0].ToString());
}
return products;
});
}
Auto-commit transactions (or implicit transactions)
An auto-commit transaction, or implicit transaction, is a basic but limited form of transaction. Such a transaction consists of only one Cypher query and is not automatically retried on failure. Therefore, any error scenarios will need to be handled by the client application itself.
Auto-commit transactions serve two purposes:
-
simple use cases such as when learning Cypher or writing one-off scripts.
-
operations such as batched data load operations, where the driver cannot be aware of the committed state and therefore cannot safely request a retry. The operator will have to perform a
retry
orundo
under these circumstances.
The driver does not retry auto-commit queries on failure since it is unaware of what state has been committed at the point of failure. |
Unlike other kinds of Cypher Query, Please refer to the following for more details: |
public async Task<List<string>> ReadProductTitles()
{
var records = new List<string>();
await using var session = Driver.AsyncSession();
// Send cypher query to the database.
// The existing IResult interface implements IEnumerable
// and does not play well with asynchronous use cases. The replacement
// IResultCursor interface is returned from the RunAsync
// family of methods instead and provides async capable methods.
var reader = await session.RunAsync(
"MATCH (p:Product) WHERE p.id = $id RETURN p.title", // Cypher query
new { id = 0 } // Parameters in the query, if any
);
// Loop through the records asynchronously
while (await reader.FetchAsync())
// Each current read in buffer can be reached via Current
{
records.Add(reader.Current[0].ToString());
}
return records;
}
Combining transactions
The driver provides a language idiomatic way to combine multiple transactions within a single asynchronous session.
public async Task<int?> EmployEveryoneInCompany(string companyName)
{
await using var session = Driver.AsyncSession();
var names = await session.ExecuteReadAsync(
async tx =>
{
var cursor = await tx.RunAsync("MATCH (a:Person) RETURN a.name AS name");
var people = await cursor.ToListAsync();
return people.Select(person => person["name"].As<string>());
});
return await session.ExecuteWriteAsync(
async tx =>
{
var relationshipsCreated = new List<int>();
foreach (var personName in names)
{
var cursor = await tx.RunAsync(
"MATCH (emp:Person {name: $person_name}) " +
"MERGE (com:Company {name: $company_name}) " +
"MERGE (emp)-[:WORKS_FOR]->(com)",
new
{
person_name = personName,
company_name = companyName
});
var summary = await cursor.ConsumeAsync();
relationshipsCreated.Add(summary.Counters.RelationshipsCreated);
}
return relationshipsCreated.Sum();
});
}
Consuming results
The asynchronous session API provides language-idiomatic methods to aid integration with asynchronous applications and frameworks.
public async Task<List<string>> GetPeopleAsync()
{
await using var session = Driver.AsyncSession();
return await session.ExecuteReadAsync(
async tx =>
{
var result = await tx.RunAsync("MATCH (a:Person) RETURN a.name ORDER BY a.name");
return await result.ToListAsync(r => r[0].As<string>());
});
}
Reactive sessions
Starting with Neo4j 4.0, the reactive processing of queries is supported. This can be achieved through reactive sessions. Reactive sessions allow for dynamic management of the data that is being exchanged between the driver and the server.
Typical of reactive programming, consumers control the rate at which they consume records from queries and the driver in turn manages the rate at which records are requested from the server. Flow control is supported throughout the entire Neo4j stack, meaning that the query engine responds correctly to the flow control signals. This results in far more efficient resource handling and ensures that the receiving side is not forced to buffer arbitrary amounts of data.
For more information about reactive stream, please see the following:
Reactive sessions will typically be used in a client application that is already oriented towards the reactive style; it is expected that a reactive dependency or framework is in place. Refer to Get started for more information on recommended dependencies. |
Lifecycle
Session lifetime begins with session construction. A session then exists until it is closed, which is typically set to occur after its contained query results have been consumed.
Transaction functions
This form of transaction requires minimal boilerplate code and allows for a clear separation of database queries and application logic. Transaction functions are also desirable since they encapsulate retry logic and allow for the greatest degree of flexibility when swapping out a single instance of server for a cluster.
Functions can be called as either read or write operations. This choice will route the transaction to an appropriate server within a clustered environment. If you are in a single instance environment, this routing has no impact but it does give you the flexibility should you choose to later adopt a clustered environment.
Before writing a transaction function it is important to ensure that any side-effects carried out by a transaction function should be designed to be idempotent. This is because a function may be executed multiple times if initial runs fail.
Any query results obtained within a transaction function should be consumed within that function, as connection-bound resources cannot be managed correctly when out of scope. To that end, transaction functions can return values but these should be derived values rather than raw results.
When a transaction fails, the driver retry logic is invoked. For several failure cases, the transaction can be immediately retried against a different server. These cases include connection issues, server role changes (e.g. leadership elections) and transient errors. Retry logic can be configured when creating a session. |
The methods ExecuteRead and ExecuteWrite have replaced ReadTransaction and WriteTransaction , which are deprecated in version 5.x and will be removed in version 6.0.
|
public IObservable<string> PrintAllProducts()
{
var session = Driver.RxSession();
return session.ExecuteRead(
tx =>
{
return tx.Run(
"MATCH (p:Product) WHERE p.id = $id RETURN p.title", // Cypher query
new { id = 0 } // Parameters in the query, if any
)
.Records()
.Select(record => record[0].ToString());
})
.OnErrorResumeNext(session.Close<string>());
}
Sessions can be configured in a number of different ways. This is carried out by supplying configuration inside the session constructor. See Session configuration for more details.
Auto-commit transactions (or implicit transactions)
An auto-commit transaction, or implicit transaction, is a basic but limited form of transaction. Such a transaction consists of only one Cypher query and is not automatically retried on failure. Therefore, any error scenarios will need to be handled by the client application itself.
Auto-commit transactions serve two purposes:
-
simple use cases such as when learning Cypher or writing one-off scripts.
-
operations such as batched data load operations, where the driver cannot be aware of the committed state and therefore cannot safely request a retry. The operator will have to perform a
retry
orundo
under these circumstances.
The driver does not retry auto-commit queries on failure since it is unaware of what state has been committed at the point of failure. |
Unlike other kinds of Cypher Query, Please refer to the following for more details: |
public IObservable<string> ReadProductTitles()
{
var session = Driver.RxSession();
return session.Run(
"MATCH (p:Product) WHERE p.id = $id RETURN p.title", // Cypher query
new { id = 0 } // Parameters in the query, if any
)
.Records()
.Select(record => record[0].ToString())
.OnErrorResumeNext(session.Close<string>());
}
Consuming results
To consume data from a query in a reactive session, a subscriber is required to handle the results that are being returned by the publisher.
Each transaction corresponds to a data flow which supplies the data from the server. Result processing begins when records are pulled from this flow. Only one subscriber may pull data from a given flow.
public IObservable<string> GetPeople()
{
var session = Driver.RxSession();
return session.ExecuteRead(
tx =>
{
return tx.Run("MATCH (a:Person) RETURN a.name ORDER BY a.name")
.Records()
.Select(record => record[0].As<string>());
})
.OnErrorResumeNext(session.Close<string>());
}
Session configuration
- Bookmarks
-
The mechanism which ensures causal consistency between transactions within a session. Bookmarks are implicitly passed between transactions within a single session to meet the causal consistency requirements. There may be scenarios where you might want to use the bookmark from one session in a different new session.
Default: None (Sessions will initially be created without a bookmark)
- Default Access Mode
-
A fallback for the access mode setting when transaction functions are not used. Typically, access mode is set per transaction by calling the appropriate transaction function method. In other cases, this setting is inherited. Note that transaction functions will ignore/override this setting.
Default: Write
- Database
-
The database with which the session will interact. When working with a database which is not the default (i.e. the
system
database or another database in Neo4j 4.0 Enterprise Edition), you can explicitly configure the database which the driver is executing transactions against.The resolution of database aliases occurs at connection creation time, which is not under the control of the session. It is therefore not recommended to alter database aliases while there are live sessions. The per-user home database is resolved at session creation and when first impersonating a user. Therefore, the creation of a new session is necessary to reflect a changed home database.
See Operations Manual → The default database for more information on databases.
See below and Cypher Manual → The
DBMS IMPERSONATE
privileges for more information on impersonation.Default: the default database as configured on the server.
See more about Database selection.
Fetch Size
-
The number of records to fetch in each batch from the server. Neo4j 4.0 introduces the ability to pull records in batches, allowing the client application to take control of data population and apply back pressure to the server. This
FetchSize
applies to simple sessions and async-sessions whereas reactive sessions can be controlled directly using the request method of the subscription.Default: 1000 records
- Impersonated User
-
Users can run transactions against the database as different users if they have been granted explicit permission for doing so. When impersonating a user, the query is run within the complete security context of the impersonated user and not the authenticated user (i.e., home database, permissions, etc.).
See Operations Manual → Per-user home databases for more information on default database per-user.
Default: None (Sessions will be created with the logged user)
Transaction configuration
Additional configuration can be provided to transaction which are executed.
Transaction timeout
A timeout value can be provided and transactions which take longer than this value to execute on the server will be terminated.
This value will override the value set by dbms.transaction.timeout
.
If no value is provided, the default is taken by the server setting
(see Operations Manual → Transaction Management).
Note: In the context of Neo4j versions 4.2 through 5.2, dbms.transaction.timeout
acts as a maximum that the driver cannot override.
For example, with a server setting of dbms.transaction.timeout=10s
the driver can specify a shorter timeout (e.g., 5 seconds) but not a value greater than 10 seconds.
If a greater value is supplied the transaction will still timeout after 10 seconds.
In Neo4j 5.3 and later releases, you can set transaction timeout to any value you wish, and it is possible for the driver to override any value set with dbms.transaction.timeout
.
Default: Configured on server with dbms.transaction.timeout
.
public void AddPerson(string name)
{
using var session = Driver.Session();
session.ExecuteWrite(
tx => tx.Run("CREATE (a:Person {name: $name})", new { name }).Consume(),
txConfig => txConfig.WithTimeout(TimeSpan.FromSeconds(5)));
}
Metadata
Transactions can be tagged with metadata which will be attached to the executing transaction and visible in various output when listing transactions and queries as well as appearing in the query log.
Default: None.
public void AddPerson(string name)
{
using var session = Driver.Session();
var txMetadata = new Dictionary<string, object> { { "applicationId", "123" } };
session.ExecuteWrite(
tx => tx.Run("CREATE (a:Person {name: $name})", new { name }).Consume(),
txConfig => txConfig.WithMetadata(txMetadata));
}