C# 针对特定EF核心查询中的未知项进行优化

C# 针对特定EF核心查询中的未知项进行优化,c#,entity-framework,entity-framework-core,C#,Entity Framework,Entity Framework Core,我有一个运行在.NET Framework上的webjob项目,其核心是EF Core 3.1。 webjob处理来自Azure服务总线的消息,并将它们保存到Azure SQL数据库中 我的问题是Azure SQL数据库为EF Core生成的查询生成了非常糟糕的查询计划。生成的查询计划的执行时间为1-2分钟。但是,当我使用选项(针对未知优化)时,执行时间会下降到0.01-0.02分钟 所以现在我想在EF Core中实现选项(针对未知优化)。我发现他们在EF Core 3.1中添加了一个DbCom

我有一个运行在.NET Framework上的webjob项目,其核心是EF Core 3.1。 webjob处理来自Azure服务总线的消息,并将它们保存到Azure SQL数据库中

我的问题是Azure SQL数据库为EF Core生成的查询生成了非常糟糕的查询计划。生成的查询计划的执行时间为1-2分钟。但是,当我使用
选项(针对未知优化)
时,执行时间会下降到0.01-0.02分钟

所以现在我想在EF Core中实现
选项(针对未知优化)
。我发现他们在EF Core 3.1中添加了一个
DbCommandInterceptor
,您可以在其中向查询添加内容:

公共类HintCommandInterceptor:DbCommandInterceptor
{
公共覆盖拦截结果读取器执行(
DbCommand命令,
CommandEventData事件数据,
截取结果)
{
//操纵命令文本等。此处。。。
command.CommandText+=“选项(针对未知优化)”;
返回结果;
}
}
但似乎这个拦截器将在每个查询上运行,我只希望它用于特定的查询。 我可以为这个拦截器实现一个单独的DbContext,但这似乎不是一个可靠的解决方案。
有人知道我如何以正确的方式实现此功能吗?

我创建了一个接口:

公共接口接口接口接口
{
bool EnableCommandInterceptors{get;set;}
}
并在我的上下文类中实现了它:

public bool EnableCommandInterceptors{get;set;}
在拦截器中,我有:

public override InterceptionResult reader执行(DbCommand命令、CommandEventData事件数据、InterceptionResult)
{
if(command.CommandText.StartsWith(“选择”)
&&eventData.Context是IInterceptable可分离的
&&可分插。EnableCommandInterceptors)
{
command.CommandText+=“选项(针对未知优化)”;
}
返回结果;
}
这允许打开和关闭此功能,如果此特定查询是上下文实例将运行的唯一查询,这就足够了。如果没有,您可以向零件添加更多条件
If(command.CommandText.StartsWith(“SELECT”)

另一种方法是使用标记特定查询,并在拦截器中查找标记文本:

if(command.CommandText.StartsWith(“选择”)
&&command.CommandText.Contains(“我的标记文本”))
{
command.CommandText+=“选项(针对未知优化)”;
}
我想附加到

如果您希望同步和async方法都能工作,例如ToListAsync(),那么您也需要重载异步版本

public class OptimizeForUnknownInterceptor : DbCommandInterceptor
{
    public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
        return base.ReaderExecuting(command, eventData, result);
    }

    public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = new CancellationToken())
    {
        command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
        return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
    }
}
公共类优化器ForUnknownInterceptor:DbCommandInterceptor
{
公共覆盖侦听结果读取器执行(DbCommand命令、CommandEventData事件数据、侦听结果)
{
command.CommandText+=“选项(针对未知优化)”;
返回base.ReaderExecuting(命令、事件数据、结果);
}
public override ValueTask ReaderExecutingAsync(DbCommand命令、CommandEventData事件数据、InterceptionResult结果、CancellationToken CancellationToken=new CancellationToken())
{
command.CommandText+=“选项(针对未知优化)”;
返回base.ReaderExecutingAsync(命令、事件数据、结果、取消令牌);
}
}
我还以更有针对性的方式使用此拦截器。因此,我可以将其应用于单个上下文:

var builder = new DbContextOptionsBuilder<CustomModelContext>();
builder.AddInterceptors(new OptimizeForUnknownInterceptor());

// Includes IConfiguration for appsettings ConnectionStrings, using dependency injection
await using (var db = new CustomModelContext(builder.Options, _configuration))
{
    ...
    var lst = await query.ToListAsync();
    ...
}
var builder=new DbContextOptionsBuilder();
AddInterceptors(新的优化器forUnknownInterceptor());
//包括使用依赖项注入的appsettings连接字符串的IConfiguration
等待使用(var db=new CustomModelContext(builder.Options,_配置))
{
...
var lst=await query.ToListAsync();
...
}
使用此方法,我创建了一个包含CustomModelContext所有构造函数的分部:

public partial class CustomModelContext
{
    private readonly IConfiguration _configuration;

    public CustomModelContext(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public CustomModelContext(DbContextOptions<CustomModelContext> options, IConfiguration configuration)
        : base(options)
    {
        _configuration = configuration;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        if (!optionsBuilder.IsConfigured)
            optionsBuilder.UseSqlServer(_configuration.GetConnectionString("CustomModelConnection"));
    }

}
公共部分类CustomModelContext
{
专用只读IConfiguration\u配置;
公共CustomModelContext(IConfiguration配置)
{
_配置=配置;
}
公共CustomModelContext(DbContextOptions选项、IConfiguration配置)
:基本(选项)
{
_配置=配置;
}
配置时受保护的覆盖无效(DBContextOptions Builder Options Builder)
{
基本配置(选项生成器);
如果(!optionBuilder.IsConfigured)
optionsBuilder.UseSqlServer(_configuration.GetConnectionString(“CustomModelConnection”));
}
}

作为参考,我正在使用.NET 5和EF Core 5

不幸的是,上下文中有多个选择。我刚刚发现扩展方法
TagWith
,也许我可以用它来区分查询?首先,您应该了解,使用此查询提示,您更有可能提高此查询的整体性能y的速度比速度慢。也许你目前正在测试的参数很快,但不会用于大多数查询。请看这篇很棒的文章,但无论如何,有一个很好的方法可以做到这一点:我知道总体性能会更差。但自bi测试以来,平均性能可能会更好g查询不再需要1-2分钟。