Repository navigation
A Compare over ranges in a query runs another method, or runs in SQL, when it isn't IComparer's own #464
Description
Found by an outside pre-tag review of release/10.x at 15085f9. The reproduction found two more cases.
Problem
RangeComparerRewriter (in ValueRangesQueryExpressionInterceptor, from #396 and #452) keeps a Compare(x, y) over ranges or sets out of SQL, because EF Core reads any int Compare(T, T) as the natural order (dotnet/efcore#39402). It replaces the call with RangeComparerCall.RunInMemory, which calls IComparer<T>.Compare or IComparer.Compare rather than the method the code called. Three cases answer differently from memory, and none raises an error:
- A different method runs. Take a comparer class whose public
Comparediffers from its explicitIComparer<T>.Compare. In aSelect,split.Compare(r.Period, p)returns0, which is the explicit implementation's answer. Memory returns1. - A
Compareon a class that implements noIComparer.RunInMemoryfinds no interface, so the call reaches EF Core unchanged and runs in SQL in the range order. This happens in a projection and also in a filter.Select(r => helper.Compare(r.Period, p))returns-1where memory returns7, andWhere(… == 7)becomesWHERE CASE WHEN r."Period" = @p THEN 0 … END = 7. - A static
Compare(DateRange, DateRange). The rewriter only handles instance calls, so it skips this one, and EF Core translates it the same way as case 2.
All three are reproduced on PostgreSQL 16 through the integration test project, over a DateRange column with p = [2026-03-01,2026-03-09].
Expected
Any Compare(x, y) over a range, a range set or a value set runs in memory as the method the code called, whatever type declares it. It fails translation in a filter or an ordering and gives memory's answer in a final projection. docs/efcore.md#what-runs-where and #396's changelog row already promise this for comparers.
Done when
- The rewrite calls the original method, instance or static. Case 1 then gives
1, and cases 2 and 3 give7in a projection and fail translation in a filter. - Translation tests and
LowerBoundComparerQueryTests(live) cover all three cases, and each test is proven by reverting the fix. - The fix lands on
mainand is cherry-picked intorelease/10.x. It ships in 10.3.0 under 10.x: RangeLowerBoundComparer.Compare in a query runs in the range order (port of #385) #396's### Changed answersrow, as A comparer of ranges or sets declared over IRange<T>, the non-generic IComparer or a value set still compares in the range order in SQL #452 did.
Activity
Merge pull request #466 from CaffeinatedCoder/fix/464-compare-origina…
Merge pull request #468 from CaffeinatedCoder/fix/464-compare-origina…