Table of Contents

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