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).
(ScheduleTagId) INCLUDE (MustStartProdBy)
– AxumOPENJSON
gives a single execution plan that is used for any size collection but this has a fixed cardinality guess that it will emit 50 rows. Maybe they should have adopted Nick Craver's original suggested solution and just parameterise the actual values with variants for 5, 10, 50, 100, 500, 1000. github.com/dotnet/efcore/issues/13617 – Carolinecarolingian