323 字
2 分鐘
EF Core Queries Inconsistent? Solve Parameter Sniffing with RECOMPILE
Conclusion
- Using
DbCommandInterceptor + TagWithallows you to addOPTION(RECOMPILE)to EF Core queries, quickly bypassing Parameter Sniffing.
Where is it suitable?
- Scenarios where the “same SQL query has unstable performance”
- EF Core query timeouts, but SSMS runs normally
- Large differences in query conditions (e.g., hot vs. cold data)
- Don’t want to modify SQL, but need a quick solution
Steps
1. Register Interceptor
- Inject a custom Interceptor into DbContext
- Allow EF Core to intercept and rewrite SQL
builder.UseSqlServer(connectionString) .UseRecompileExtensions(); // Register interceptor2. Tag Query with RECOMPILE
- Use
TagWithfor tagging (built-in EF Core) - Let the Interceptor know which SQL segment to process
var name = "SomeName";var result = context.SomeItems .Where(x => x.Name == name) .WithRecompile() // Add tag .ToList();Actual SQL:
SELECT [s].[Id], [s].[Name]FROM [SomeItems] AS [s]WHERE [s].[Name] = @__name_0OPTION(RECOMPILE)3. Implement Interceptor to Rewrite SQL
- Detect Tag
- Remove comment and add
OPTION(RECOMPILE)
public static class RecompileExtensions{ private const string RecompileTag = "recompile_query_tag"; private const string RecompileComment = "-- " + RecompileTag + "\r\n";
public static DbContextOptionsBuilder UseRecompileExtensions(this DbContextOptionsBuilder builder) { return builder.AddInterceptors(RecompileInterceptor.Instance); }
public static IQueryable<T> WithRecompile<T>(this IQueryable<T> query) { return query.TagWith(RecompileTag); }
private class RecompileInterceptor : DbCommandInterceptor { public static RecompileInterceptor Instance = new();
public override InterceptionResult<DbDataReader> ReaderExecuting( DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result) { CorrectCommand(command); return base.ReaderExecuting(command, eventData, result); }
private static void CorrectCommand(DbCommand command) { var newQuery = command.CommandText.Replace(RecompileComment, "");
if (!ReferenceEquals(newQuery, command.CommandText)) { if (newQuery.StartsWith("\r\n")) newQuery = newQuery.Substring(2);
newQuery += "\r\nOPTION(RECOMPILE)"; command.CommandText = newQuery; } } }}Additional Notes
RECOMPILEgenerates a new execution plan every time, which increases CPU cost and is not suitable for high-frequency queries.- Suitable for “unstable queries” rather than “inherently slow queries.”
- If it’s a global issue, consider disabling Parameter Sniffing at the DB level.
ALTER DATABASE SCOPED CONFIGURATION SET PARAMETER_SNIFFING = OFF;Commands / Examples Summary
Click to expand
// Registerbuilder.UseRecompileExtensions();
// Usagequery.WithRecompile().ToList();-- Disable Parameter Sniffing at DB levelALTER DATABASE SCOPED CONFIGURATIONSET PARAMETER_SNIFFING = OFF;Conclusion
- When EF and SQL performance are inconsistent, this trick can often quickly stop the bleeding; however, long-term, it’s still necessary to optimize indexes and queries.
References
EF Core Queries Inconsistent? Solve Parameter Sniffing with RECOMPILE
https://joyceowo.github.io/posts/en/23ba78ea09fa8171ba34eeb03351b2a5/