Configuring the Entity Framework database context
Neos uses an Entity Framework Core DbContext to communicate with the database.
It can sometimes be useful to customize the behavior of this context. For example, you may need to execute a SQL command when Neos connects to the database.
Implementing a configuration class
In order to be able to configure the database context, you need to create a class containing a static method called Configure
.
This method must takes two parameters. The IServiceProvider
parameter allows you to get services configured for dependency injection while the DbContextOptionsBuilder
allows you to customize the configuration of the database context.
In the following example, we create a configuration class that adds an interceptor for customizing the behavior when certain events occur.
public static class SetLoginConfigurator
{
public static void Configure(IServiceProvider provider, DbContextOptionsBuilder options)
{
options.AddInterceptors(ActivatorUtilities.CreateInstance<SetLoginInterceptor>(provider));
}
}
Registering the configuration class
In the same way as for value converters, configuration classes need to be registered in the YAML configuration of the cluster.
Csprojs:
Persistence:
- ./projects/Persistence/MyProject/GroupeIsa.Northwind.MyProject.csproj
DatabaseContextConfigurators:
- Name: SetLoginConfigurator
Type: MyProject.SetLoginConfigurator
In the Csprojs/Persistence
section, we need to reference the projects containing the configuration classes.
Then, in the DatabaseContextConfigurators
section, we list all the configuration classes we want to use.
When the database context is configured, Neos will automatically call the Configure
method of the referenced configuration classes.
Interceptors
The previous example was referring to interceptors. Interceptors are classes that can change the behavior of Entity Framework Core operations.
The following example shows the interceptor added to the database context in the SetLoginConfigurator
class seen above :
public static class SetLoginConfigurator
{
public static void Configure(IServiceProvider provider, DbContextOptionsBuilder options)
{
options.AddInterceptors(ActivatorUtilities.CreateInstance<SetLoginInterceptor>(provider));
}
}
internal class SetLoginInterceptor : DbConnectionInterceptor
{
private readonly IPersistenceContext _persistenceContext;
private readonly IUserInfoAccessor _userInfoAccessor;
public SetLoginInterceptor(IPersistenceContext persistenceContext, IUserInfoAccessor userInfoAccessor)
{
_persistenceContext = persistenceContext;
_userInfoAccessor = userInfoAccessor;
}
public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData)
{
// Executing the code only for SQL Server database and when writing data
if (_persistenceContext.IsReadOnly
|| connection is not Microsoft.Data.SqlClient.SqlConnection)
{
return;
}
// Creating a command that adds user information to the SQL Server SESSION_CONTEXT
string login = _userInfoAccessor.User?.Identifier ?? "no_user";
string detailedLogin = _userInfoAccessor.User != null ? $"{login} - {_userInfoAccessor.User.Email}" : login;
using DbCommand command = connection.CreateCommand();
command.CommandText = @$"EXEC sp_set_session_context 'Login', '{ login }'
EXEC sp_set_session_context 'DetailedLogin', '{detailedLogin}'";
// Executing the command
command.ExecuteNonQuery();
}
public override Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default)
{
// Executing the same code whether an asynchronous connection is used or not
ConnectionOpened(connection, eventData);
return Task.CompletedTask;
}
}
The interceptor in this example gets the current user and executes a query that add data in the SESSION_CONTEXT
of a SQL Server database.
Interceptor classes must inherit from DbCommandInterceptor
, DbConnectionInterceptor
, or DbTransactionInterceptor
and override the methods corresponding to targeted events.
Warning
Some methods can have multiple signatures which may not all be used. Be sure to check if your interceptor is called and override another signature if it is not the case.
Note
If you need to know which type of database ise used when the code is executed, you need to reference the following packages in your C# project :
- SQL Server : Microsoft.EntityFrameworkCore.SqlServer
- Oracle : Oracle.EntityFrameworkCore
- PostgresSQL : Npgsql.EntityFrameworkCore.PostgreSQL
Use the following classes to check the type of the DbConnection
:
- SQL Server : Microsoft.Data.SqlClient.SqlConnection
- Oracle : Oracle.ManagedDataAccess.Client.OracleConnection
- PostgresSQL : Npgsql.NpgsqlConnection