【问题标题】:Can I fire an event on connect database in Entity Framework Core?我可以在 Entity Framework Core 中的连接数据库上触发事件吗?
【发布时间】:2017-07-24 02:25:26
【问题描述】:

我有一个 DbContext 可以访问我的 Postgresql 数据库,但是当连接会话与数据库开始时,我需要运行一个小的 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')";
    }

【问题讨论】:

  • 听起来是个XY问题:你问怎么做Y,但问题出在前面选择的路径X上。为什么一定要设置这个用户名,你想怎么用它以及为什么您必须设置它每次打开连接时?目前这个问题缺乏足够的背景来回答。
  • 我认为你可以解决这个问题,为 StateChange 事件添加一个事件处理程序到你的 DbContext 数据库属性

标签: c# postgresql entity-framework-core


【解决方案1】:

您应该能够通过将连接传递到您的 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);

【讨论】:

    【解决方案2】:

    如果您使用的是 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);
        }
    }
    

    ConnectionOpenedAsync() 在您使用异步方法访问数据库时被调用。

    要连接这个拦截器以便上下文知道它,你必须像这样将它添加到 Startup.cs 中的 AddDbContext 调用中。

    services.AddDbContext<ApplicationDbContext>(options => options
                .UseNpgsql(connection)
                .AddInterceptors(new DbUserIdProvider()));
    

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

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

    这允许我在 Interceptor 中为 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);
        }
    }
    

    【讨论】:

    • 有没有办法将 session_context 设置在与连接打开“命令”相同的往返行程中?
    • Iúri dos Anjos,我认为在 SQL Server 的 session_context 中设置一个值只是一个 DB 函数调用。如果是这样,您应该能够以与我在上面设置 client_user_id 相同的方式进行操作,只是使用不同的语句。每次打开数据库连接时都会执行。有关更多信息,请参阅本文sqlshack.com/sql-server-session_context-function-with-examples
    猜你喜欢
    • 2019-12-28
    • 2021-10-02
    • 2018-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-03
    相关资源
    最近更新 更多