EF Core 8 now inlines values in where statements using a combination of WHERE IN and OPENJSON as opposed to the previous WHERE IN(...).
This change is noted in the documentation and the stated reason is as follows:
The inlining of values here is done in such a way that there is no chance of a SQL injection attack. The change to use JSON described below is all about performance, and nothing to do with security.
Unfortunately, OPENJSON performance on our 2017 SQL Server instance is poor.
The query below, generated by EF Core 8, takes 1.8 seconds to run and results in nearly 400,000 reads:
DECLARE @__scheduleTagIds_0 nvarchar(4000) = N'[5835,5970,6563,6564,6565,6645,6835,6850,7034,7127]';
SELECT [s].[ScheduleTagId]
,[s].[MustStartProdBy]
FROM [ScheduleTagMustStartBy] AS [s]
WHERE [s].[ScheduleTagId] IN (
SELECT [s0].[value]
FROM OPENJSON(@__scheduleTagIds_0) WITH ([value] int '$') AS [s0]
)
If I refactor the query to use a standard WHERE IN(...), the execution time drops to 120ms and 29,000 reads:
SELECT [s].[ScheduleTagId]
,[s].[MustStartProdBy]
FROM [ScheduleTagMustStartBy] AS [s]
WHERE [s].[ScheduleTagId] IN (5835,5970,6563,6564,6565,6645,6835,6850,7034,7127)
I have hundreds of queries in my application that use .Where(x => [collection].Contains(x.Id)) and I'm very concerned about the performance degradation I'm seeing in select queries.
Question
What can I do to to mitigate this issue? I'm open to options either in EF or on the SQL Server side (though don't want to change the compatibility level of the database).
You don't have to change the DB compatibility level to 120. You can call
UseCompatibilityLevel(120)to tell EF to generate the old style SQL regardless of what the compat level is actually set to.There will also be an option to avoid this global setting and just constant-ize the specific queries that are problematic. But I don't think this
EF.Constantapproach has made its way to the official release yet so you would need to use a daily build.It certainly can use indexes with
OPENJSONit just might choose not to.When you pass constants it can determine
With
OPENJSONit can't tell any of that so it just falls back to guesses. This may lead to a different (worse) plan.