我需要使用“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>();
如果这是一个普遍问题,为什么我的查询会按预期工作?另一方面,为什么我的健康检查失败?
我尝试使用 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)