I have a webjob project running on .NET Framework with EF Core 3.1. The webjob processes messages from an Azure Service Bus and saves them into an Azure SQL Database.
The problem I have is that the Azure SQL Database generates really bad query plans for the query that EF Core generated. With the generated query plan the execution time is 1-2 minutes. However when I use OPTION (OPTIMIZE FOR UNKNOWN)
the execution time drops down to 0.01 - 0.02 minutes.
So now I want to implement the OPTION (OPTIMIZE FOR UNKNOWN)
in EF Core. I have found that they added a DbCommandInterceptor
in EF Core 3.1 where can you append things to your query: MSDOCS
public class HintCommandInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
// Manipulate the command text, etc. here...
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
return result;
}
}
But it seems like this interceptor will run on every query and I only want it for a specific query. I could implement a seperate DbContext for this interceptor but that doesn't seem like a solid solution. Does anyone have an idea how I could implement this in a correct way?
I created an interface:
public interface IInterceptable
{
bool EnableCommandInterceptors { get; set; }
}
And implemented it in my context class:
public bool EnableCommandInterceptors { get; set; }
And in the interceptor I have:
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
if(command.CommandText.StartsWith("SELECT")
&& eventData.Context is IInterceptable intercepatable
&& intercepatable.EnableCommandInterceptors)
{
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
}
return result;
}
This allows turning this feature on and off, which may be enough if this specific query is the only query that a context instance will run. If not, you could add more conditions to the part if(command.CommandText.StartsWith("SELECT")
.
Another way is to tag a specific query with .TagWith
and look for the tag text in the interceptor:
if (command.CommandText.StartsWith("SELECT")
&& command.CommandText.Contains("my tagged text"))
{
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
}
I'd like to append to Gert Arnold's post ...
If you want both synchronous and async methods to work, for example ToListAsync() , then you need to overload the Async version, too.
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);
}
}
I also use this interceptor in more targeted fashion. So I can apply it to a single Context:
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();
...
}
Using this, I created a partial with all the constructors for 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"));
}
}
For reference, I'm using .NET 5 and EF Core 5
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.