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