Repository navigation
fix: run a Compare over ranges in a query as the method the code called (#464) - #466
Merged
Merged
Conversation
…ed (#464) RangeComparerRewriter replaced comparer.Compare(x, y) with a call to IComparer<T>.Compare, so a class whose public Compare differs from its explicit implementation answered the explicit one's 0 where memory answers 1. It also required an instance whose type implements IComparer, so a Compare on a class that isn't a comparer, or a static one, went to EF Core unchanged and ran in SQL in the range order, in filters as well as projections. The rewrite now hands RangeComparerCall.RunInMemory a compiled delegate of the call as written, held in a registry and referenced by an int id: EF Core refuses a delegate constant in a client projection. Filters and orderings still fail translation, and the error still names RangeComparerCall.RunInMemory. Found-By: RV Defect-Class: SQL Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 25, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #464
Problem
#385 and #452 keep a
Compare(x, y)over ranges or sets out of SQL, because EF Core reads anyint Compare(T, T)as the natural order (dotnet/efcore#39402). The rewrite replaced the call withRangeComparerCall.RunInMemory, and that calledIComparer<T>.Compareinstead of the method the code called. An outside pre-tag review ofrelease/10.xfound the first case below; the reproduction found the other two. On PostgreSQL 16, withp = [2026-03-01,2026-03-09]:split.Compare(r.Period, p)in aSelect: publicCompareand explicitIComparer<T>.Comparediffer0, from the explicit implementation1helper.Compare(r.Period, p)on a class that implements noIComparerCASE WHEN r."Period" = @p THEN 0 …, inSelectand inWhereLowerBound.Compare(r.Period, p)No error in any of them. The docs promise that a comparer's
Compareover ranges runs in memory only.Change
RangeComparerCall:RunInMemory<TComparer, T>(comparer, x, y, int call)andRunInMemory<T>(x, y, int call)run a compiled delegate of the call as written. The delegate sits in a registry, one per method and receiver type, and the query holds only itsintid. EF Core rejects a delegate constant in a client projection because the cached query would capture it, but it accepts a constant of a type it maps. A filter or an ordering still fails translation, and the message still namesRangeComparerCall.RunInMemory.RangeComparerRewriter: rewrites anyint Compare(T, T)with a range, range set or value set operand, static or instance, whatever type declares it. That is exactly the set of calls EF Core'sComparisonTranslatortakes. TheIComparer/IComparer<T>lookup and the non-generic overload are gone.docs/efcore.md(What runs where),docs/migration.md, claim E31 and the changelog rows (root and EF Core) cover the staticCompare, aCompareon a class that isn't a comparer, and the method that runs.Tests
ClientSideOnlyTranslationTests:WhereandOrderByrows for aCompareon a class that isn't a comparer, and for a staticCompare.LowerBoundComparerQueryTests(live): the projection test agrees with memory for all three cases (the split comparer's explicit implementation compares in the range order), and the filter/ordering test expectsRunInMemorytranslation failures for them.RangeComparerCalland rewriter back, the new tests kept): 4 translation rows fail; the live filter query returns rows instead of throwing; the live projection getsserver 0,1,-1,1,-1againstmemory 0,0,0,1,-1for the split comparer.LowerBoundComparerQueryTests,ClientSideOnlyProjectionTests,TranslationSupportProbeTestsandCapturedCollectionProbeTestspass on PostgreSQL 16. The seedlower-bound-comparer-runs-as-sql.patchstill applies.🤖 Generated with Claude Code