323 字
2 分鐘
瀏覽次數
EF Core Queries Inconsistent? Solve Parameter Sniffing with RECOMPILE
2025-07-25
2026-04-10

Conclusion#

  • Using DbCommandInterceptor + TagWith allows you to add OPTION(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 interceptor

2. Tag Query with RECOMPILE#

  • Use TagWith for 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_0
OPTION(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#

  • RECOMPILE generates 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
// Register
builder.UseRecompileExtensions();
// Usage
query.WithRecompile().ToList();
-- Disable Parameter Sniffing at DB level
ALTER DATABASE SCOPED CONFIGURATION
SET 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/
作者
JoyceOwO
發佈於
2025-07-25
許可協議
CC BY-NC-SA 4.0