DBContext 池不能与拦截器和运行状况检查一起使用

问题描述 投票:0回答:1

我需要使用“OPTION (OPTIMIZE FOR UNKNOWN)”来优化一些sql查询。

因此我添加了一个像这样的自定义拦截器:

public class QueryHintInterceptor : DbCommandInterceptor
{
    private DbCommand GetCommandWithQueryHint(DbCommand command)
    {
        if (command.CommandText.StartsWith($"-- {Const.TagWith.OptimizeForUnknown}") && !command.CommandText.EndsWith(Const.TagWith.OptimizeForUnknown))
            command.CommandText += $" {Const.TagWith.OptimizeForUnknown}";

        return command;
    }

    public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        return base.ReaderExecuting(GetCommandWithQueryHint(command), eventData, result);
    }

    public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
    {
        return base.ReaderExecutingAsync(GetCommandWithQueryHint(command), eventData, result, cancellationToken);
    }
}

我添加了一个扩展方法:

    public static IQueryable<TEntity> OptimizeForUnknown<TEntity>(this IQueryable<TEntity> dbset) where TEntity : ImportEntity
    {
        return dbset.TagWith(Const.TagWith.OptimizeForUnknown); //we set this tag and detect it on execution to inject the desired sql
    }

我在OnConfiguring中注册了它如微软本身所述

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);

        optionsBuilder.AddInterceptors(new QueryHintInterceptor()); 
        }
    }

所以我可以这样使用它:

var query = icrmContext.Set<TEntity>()
    .AsNoTracking()
    .OptimizeForUnknown();

这对于我想要的查询来说效果很好,但不幸的是健康检查现在开始失败:

{
    "status": "Unhealthy",
    "results": {
        "MyDbContext": {
            "status": "Unhealthy",
            "description": "'OnConfiguring' cannot be used to modify DbContextOptions when DbContext pooling is enabled.",
            "responseTime": "861,8354",
            "data": {}
        }
    }
}

好吧,我将 DbContext 池与 AddDbContextPool 一起使用,让我们在那里配置它,我想:

        services.AddDbContextPool<MyDbContext>((sp, options) =>
        {
            options
                .UseSqlServer(sqlConnectionString, opts =>
                {
                    opts.CommandTimeout(commandTimeoutInSeconds);
                    opts.EnableRetryOnFailure();
                })
                .AddInterceptors(ServiceProviderExtensions.GetRequiredService<QueryHintInterceptor>(sp));
        });

在这些更改之后,健康检查再次工作,但我的拦截器不再工作(QueryHintInterceptor 中没有断点被击中)。

这是我的健康检查配置:

var healthChecksBuilder = services.AddHealthChecks()
    .AddDbContextCheck<MyDbContext>();

如果这是一个普遍问题,为什么我的查询会按预期工作?另一方面,为什么我的健康检查失败?

c# entity-framework-core interceptor health-check asp.net-core-8
1个回答
0
投票

我尝试使用 DbContextPool 和 DbContextHealthCheck 通过拦截器重现此内容,以防存在一些潜在冲突,并且它们工作正常。当你调试拦截器时,你的断点是在“GetCommandWithQueryHint”的第一行吗?我唯一能想到的另一件事是,您的依赖项注入可能无法通过注册的池提供程序解析 DbContext 实例。 (虽然你的健康检查似乎表明是这样)

我使用的拦截器的实现可以在这里找到:https://github.com/StevePy/Ag.Isle.EF.Core/tree/main/Ag.Isle.EF.Core/Interceptors随意拥有看看这个实现可以通过扩展方法附加多个选项拦截器,以便它处理多个标签并组成选项部分。

我初始化它的代码包括:

       builder.Services.AddHealthChecks()
          .AddDbContextCheck<TestDbContext>();
       builder.Services.AddDbContextPool<TestDbContext>((sp, options) =>
       {
           options.UseSqlServer(connectionString)
               .AddInterceptors(new RecompileInterceptor());
       });

效果很好,当我将其添加到简单的测试运行时,重新编译拦截器被调用:

    public IActionResult Index()
    {
        var searches = _context.Searches
            .WithRecompile()
            .ToList();
        return View();
    }

拦截器中的断点被命中并添加了选项。

-- {{RECOMPILE}}

SELECT [s].[SearchId], [s].[AppId], [s].[CreatedAt], [s].[CreatedBy], [s].[Json], [s].[LastModAt], [s].[LastModBy], [s].[Name], [s].[RowVersion], [s].[TypeName], [s].[UserId]
FROM [PSS].[Searches] AS [s] OPTION (RECOMPILE)
© www.soinside.com 2019 - 2024. All rights reserved.