查询拦截-处理诊断侦听器 [英] Query Interception - Disposing of diagnostic listeners

查看:138
本文介绍了查询拦截-处理诊断侦听器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

We are using DiagnosticListeners in order to modify the SQL command text that EF Core produces. The problem is that our listeners need to be modifying the SQL command based on some user specific data that comes into our Api via HttpRequests. Our current solution is extremely hacky and likely to cause issues in the future. We register a new listener each time DbContext is created:

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
}

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

The simplified listener code looks like below:

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 }
} 

如您所见,我们当前的方法是使用 connectionId 每次创建 DbContext 时都不同。采用这种骇人听闻的方法的原因是,即使每次 HttpRequest DbContext.Dispose()时,侦听器实例也不会被释放。 c $ c>已处理。因此, connectionId 基本上可以使侦听器与给定的 DbContext 实例之间具有1:1映射的错觉。

As you can see, our current approach is to use the connectionId which will be different each time DbContext is created. The reason for this hacky approach is because the listener instances are not disposed even though the DbContext.Dispose() is called every time the HttpRequest is processed. So connectionId allows to basically have an illusion of 1:1 mapping between a listener and a given DbContext instance.

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

What happens though is that the amount of listener instances keep piling up throughout the lifetime of the api and the only time the instances go away is when the application pools stop or recycle.

是否有可能以某种方式处置这些侦听器实例?我也愿意采用另一种方法来修改SQL命令(诊断侦听器是我们发现的EF Core唯一可行的方法)。

Is it possible to somehow dispose of these listener instances and how? I'm also open to a different approach in order to modify SQL commands (the diagnostic listeners was the only viable one we found for EF Core).

编辑:
我仅修改 SELECT 命令。我省略了详细信息,但是 DbCommandAdapter 是使用用户特定的前缀创建的,该前缀根据尝试访问API的用户而有所不同。

I am modifying only SELECT commands. I omitted the details, but the DbCommandAdapter gets created with a user specific prefix that is different based on the user trying to access the API.

例如,如果查询为:

从雇员中选择FIELD1,FIELD2

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

and the user specific prefix is USER_SOMENUMBER, then the modified query ends up:

从USER_SOMENUMBER_EMPLOYEES中选择FIELD1,FIELD2

我知道这很脆弱,但我们保证

I understand that this is fragile but we guarantee that the schema of the tablename that we change is identical and it is not a concern.

推荐答案

如果您无法处理该表名的架构,则不必担心。听众为什么不合并它们并重用它们?当配置或构造很昂贵时,合并是一种很好的软件模式。防止无限增长也是一种合理的用法。

If you can't dispose of the listeners why not pool them and reuse them? Pooling is a good software pattern when disposing or constructing is expensive. Preventing infinite growth is a reasonable usage too.

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

The following is only pseudo-code. It requires knowing when an adapter transaction has completed, so that it can be marked available and reused. It also assumes the updated myDbContext will have what you need to perform your work.

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的依赖项注入到CustomSqlServerQuerySqlGeneratorFactory中,然后在CustomSqlServerQuerySqlGenerator中可用。请参阅: Ef-Core-我可以使用什么正则表达式在Db Interceptor中用nolock替换表名

NOTE: I haven't tried this myself. Ivan Stoev recommends injecting dependency to ICurrentDbContext to CustomSqlServerQuerySqlGeneratorFactory which is then available inside CustomSqlServerQuerySqlGenerator. see: Ef-Core - What regex can I use to replace table names with nolock ones in Db Interceptor

这篇关于查询拦截-处理诊断侦听器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆