-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
Bug description
While testing a complex query in templated form to find the most performant query I noticed EF Core 8 would produce a very suboptimal query with a ternary on a null-condition, while the not-null ternary was behaving fine. This resulted in the not-null ternary executing in about ~6s, while the null-conditioned ternary would run in ~33s.
Upgrading the codebase to EF Core 9 (in order to file this bug report) I noticed the not-null conditioned ternary had regressed to the same performance as the null conditioned ternary due to changes in the generated SQL.
Your code
I'm using the following predicate as part of the template:
r => Template.SupplyOrderId != null
? r.SourcePallet.SupplyOrderId == Template.SupplyOrderId
: r.SourcePallet.Id == Template.ActivePalletIdIn EF Core 8, this resulted in the following SQL (subquery only):
SELECT [w].[NetWeight], 1 AS [Key]
FROM [WasteRegistrations] AS [w]
INNER JOIN [SourcePallet] AS [s3] ON [w].[SourcePalletId] = [s3].[Id]
WHERE CASE
WHEN [s].[SupplyOrderId] IS NOT NULL THEN CASE
WHEN [s3].[SupplyOrderId] = [s].[SupplyOrderId] AND [s3].[SupplyOrderId] IS NOT NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
ELSE CASE
WHEN [s3].[Id] = [p3].[ActivePalletId] AND [p3].[ActivePalletId] IS NOT NULL THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END
END = CAST(1 AS bit)Here p3.ActivePalletId is int?, s.SupplyOrderId is int? as well.
in EF Core 9, the subquery is as follows:
SELECT [w].[NetWeight], 1 AS [Key]
FROM [WasteRegistrations] AS [w]
INNER JOIN [SourcePallet] AS [s4] ON [w].[SourcePalletId] = [s4].[Id]
WHERE CASE
WHEN [s].[SupplyOrderId] IS NOT NULL THEN ~CAST([s4].[SupplyOrderId] ^ [s].[SupplyOrderId] AS bit)
ELSE ~CAST([s4].[Id] ^ [p3].[ActivePalletId] AS bit)
END = CAST(1 AS bit)The EF8 query results in a filter on the SourcePallet table, which in turn requires a Clustered Index Seek on the WasteRegistrations table to find the accompanying waste registrations. With EF9 however, the subquery causes a CLustered Index Scan, because (my assumption) it can no longer digest what source it should use to match the s4.SupplyOrderId part of the query. I also have my doubts how this behavior will affect filtered indexes, but I think it renders them moot due to the way the query is now built.
Stack traces
NA.
Verbose output
NA.
EF Core version
9.0.6
Database provider
Microsoft.EntityFrameworkCore.SqlServer
Target framework
.NET 8.0
Operating system
Windows 11
IDE
Visual Studio 2022 17.4