我可以在 Entity Framework Core 中的连接数据库上触发事件吗?

Luc*_*rez 7 c# postgresql entity-framework-core

我有一个 DbContext 可以访问我的 Postgresql DB,但是当连接会话与 DB 启动时,我需要运行一个小 SQL 命令。我需要为每次交互执行此操作。更具体地说,它是一个用于设置记录用户名的会话变量的函数。

可以在 EF Core 中做一些事情来处理这个问题吗?

- 解决方案 -

我没有意识到我可以像 bricelam 所说的那样直接在 OnConfiguring 中指定连接。我需要在每个连接中执行此操作,因为它是会话变量。它不是数据库的用户名,而是应用程序日志系统的用户名。

    public ContratoInternetDbContext(DbContextOptions<ContratoInternetDbContext> options, 
        IOptions<AppSettings> configs)
        : base(options)
    {
        _appSettings = configs.Value;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var conn = new NpgsqlConnection(_appSettings.ConnectionString);
        conn.StateChange += (snd, e) =>
        {
            if ((e.CurrentState != e.OriginalState) && (e.CurrentState == ConnectionState.Open))
            {
                _cmmSetVarSession.ExecuteNonQuery();
            }
        };

        optionsBuilder.UseNpgsql(conn);

        _cmmSetVarSession = conn.CreateCommand();
        _cmmSetVarSession.CommandText = "select sessao_set_var('usuario', 'CENTRAL_CLIENTE')";
    }
Run Code Online (Sandbox Code Playgroud)

GMK*_*GMK 8

如果您使用 EF Core 3.0 或更高版本,您现在可以使用 DBConnectionInterceptor实现此目的。执行您想要的操作的代码看起来像这样。

public class DbUserIdProvider : DbConnectionInterceptor
{
    // Called just after EF has called Open().
    public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData)
    {
        base.ConnectionOpened(connection, eventData);
        var cmd = connection.CreateCommand();
        cmd.CommandText = "set session.client_user_id to 'myid'";
        cmd.ExecuteNonQuery();
    }

    // Called just after EF has called OpenAsync().
    public override Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default)
    {
        var cmd = connection.CreateCommand();
        cmd.CommandText = "set session.client_user_id to 'myid'";
        cmd.ExecuteNonQuery();
        return base.ConnectionOpenedAsync(connection, eventData, cancellationToken);
    }
}
Run Code Online (Sandbox Code Playgroud)

当您使用异步方法访问数据库时,会调用 ConnectionOpenedAsync()。

要连接此拦截器以便上下文了解它,您必须将其添加到 Startup.cs 中的 AddDbContext 调用,如下所示。

services.AddDbContext<ApplicationDbContext>(options => options
            .UseNpgsql(connection)
            .AddInterceptors(new DbUserIdProvider()));
Run Code Online (Sandbox Code Playgroud)

编辑:自从我写这篇文章以来,我找到了一种更好的方法将拦截器添加到数据库上下文。我现在就是这样做的。

services.AddScoped<DbUserIdInterceptor>();

services.AddDbContext<ApplicationDbContext>((provider, options) =>
{
    options.UseNpgsql(applicationSettings.ConnectionString());
    // Resolve the DbUserIdInterceptor from the service provider
    options.AddInterceptors(provider.GetRequiredService<DbUserIdInterceptor>());
});
Run Code Online (Sandbox Code Playgroud)

这允许我在拦截器中使用构造函数注入进行 DI,现在看起来像这样。

public class DbUserIdInterceptor : DbConnectionInterceptor
{
    UserInfoService userInfoService;
    public DbUserIdInterceptor(UserInfoService uis)
    {
        userInfoService = uis;
    }

    // Called just after EF has called OpenAsync().
    public override Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken = default)
    {
        var cmd = connection.CreateCommand();
        cmd.CommandText = $"set session.client_user_id to '{userInfoService.uniqueName}'";
        cmd.ExecuteNonQuery();
        return base.ConnectionOpenedAsync(connection, eventData, cancellationToken);
    }
}
Run Code Online (Sandbox Code Playgroud)


bri*_*lam 3

您应该能够通过将连接传递到您的连接DbContext并挂钩StateChange事件来完成此操作:(请原谅 SQLite 示例。我知道您说的是 PostgreSQL。)

var connection = new SqliteConnection(connectionString);
_connection.StateChange += (sender, e) =>
{
    if (e.OriginalState != ConnectionState.Open)
        return;

    var senderConnection = (DbConnection)sender;

    using (var command = senderConnection.CreateCommand())
    {
        command.Connection = senderConnection;
        command.CommandText = "-- TODO: Put little SQL command here.";

        command.ExecuteNonQuery();
    }
};

optionsBuilder.UseSqlite(connection);
Run Code Online (Sandbox Code Playgroud)