gpt4 book ai didi

c# - 查询拦截 - 处理诊断监听器

转载 作者:太空宇宙 更新时间:2023-11-03 12:07:45 26 4
gpt4 key购买 nike

我们正在使用 DiagnosticListeners 来修改 EF Core 生成的 SQL 命令文本。问题是我们的监听器需要根据通过 HttpRequests 进入我们的 Api 的一些用户特定数据来修改 SQL 命令。我们当前的解决方案非常老套,将来可能会导致问题。每次创建 DbContext 时,我们都会注册一个新的监听器:

public class MyContext : DbContext
{
private readonly HReCommandAdapter _adapter;


public MyContext(DbContextOptions options) : base(options)
{
_adapter = new DbCommandAdapter();

var listener = this.GetService<DiagnosticSource>();
(listener as DiagnosticListener).SubscribeWithAdapter(_adapter);
}

public override void Dispose()
{
_adapter.Dispose();
base.Dispose();
}

//DbSets and stuff
}

简化的监听器代码如下所示:

public class DbCommandAdapter : IDisposable
{
private bool _hasExecuted = false;
private Guid? _lastExecId = null;

[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting")]
public void OnCommandExecuting(DbCommand command, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, DateTimeOffset startTime)
{
if (!_lastExecId.HasValue)
_lastExecId = connectionId;

if (_lastExecId != connectionId)
return;

//We are modifying command text here
}

[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted")]
public void OnCommandExecuted(object result, bool async)
{
}

[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandError")]
public void OnCommandError(Exception exception, bool async)
{
}

public void Dispose() { //No code in here }
}

如您所见,我们当前的方法是使用每次创建 DbContext 时都不同的 connectionId。之所以采用这种 hacky 方法,是因为即使每次处理 HttpRequest 时都会调用 DbContext.Dispose(),也不会释放监听器实例。因此,connectionId 基本上允许在监听器和给定的 DbContext 实例之间产生 1:1 映射的错觉。

但是发生的是,在 API 的整个生命周期中,监听器实例的数量不断堆积,并且实例消失的唯一时间是应用程序池停止或回收时。

是否有可能以某种方式处理这些监听器实例以及如何处理?我也愿意采用不同的方法来修改 SQL 命令(诊断监听器是我们为 EF Core 找到的唯一可行的方法)。

编辑:我只修改 SELECT 命令。我省略了细节,但是 DbCommandAdapter 创建时带有用户特定的前缀,该前缀因尝试访问 API 的用户而异。

例如,如果查询是:

SELECT FIELD1, FIELD2 FROM EMPLOYEES

并且用户特定前缀是USER_SOMENUMBER,然后修改后的查询结束:

从 USER_SOMENUMBER_EMPLOYEES 中选择 FIELD1、FIELD2

我知道这很脆弱,但我们保证我们更改的表名的架构是相同的,这不是问题。

最佳答案

如果您不能处理监听器,为什么不将它们集中起来并重用它们呢?当配置或构建成本高昂时,池化是一种很好的软件模式。防止无限增长也是一种合理的用法。

以下仅为伪代码。它需要知道适配器事务何时完成,以便它可以标记为可用和重用。它还假定更新后的 myDbContext 将具有执行工作所需的内容。

public static class DbCommandAdapterPool
{
private static ConcurrentBag<DbCommandAdapter> _pool = new ConcurrentBag<DbCommandAdapter>();

public static DbCommandAdapter SubscribeAdapter(MyContext context)
{
var adapter = _pool.FirstOrDefault(a => a.IsAvailable);
if (adapter == null)
{
adapter = new DbCommandAdapter(context);
_pool.Add(adapter);
}
else adapter.Reuse(context);

return adapter;
}
}

public class MyContext : DbContext
{
private readonly HReCommandAdapter _adapter;


public MyContext(DbContextOptions options) : base(options)
{
//_adapter = new DbCommandAdapter();

//var listener = this.GetService<DiagnosticSource>();
//(listener as DiagnosticListener).SubscribeWithAdapter(_adapter);

DbCommandAdapterPool.SubscribeAdapter(this);
}

public override void Dispose()
{
_adapter.Dispose();
base.Dispose();
}

//DbSets and stuff
}

public class DbCommandAdapter : IDisposable
{
private bool _hasExecuted = false;
private Guid? _lastExecId = null;
private MyContext _context;
private DiagnosticListener _listener;//added for correlation

public bool IsAvailable { get; } = false;//Not sure what constitutes a complete transaction.

public DbCommandAdapter(MyContext context)
{
this._context = context;
this._listener = context.GetService<DiagnosticSource>();
}


...

public void Reuse(MyContext context)
{
this.IsAvailable = false;
this._context = context;
}
}

注意:我自己还没有尝试过。 Ivan Stoev 建议将对 ICurrentDbContext 的依赖注入(inject)到 CustomSqlServerQuerySqlGeneratorFactory,然后在 CustomSqlServerQuerySqlGenerator 中可用。参见:Ef-Core - What regex can I use to replace table names with nolock ones in Db Interceptor

关于c# - 查询拦截 - 处理诊断监听器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54038867/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com