Skip to content

fix: let a query filter order the context's own values without a lambda - #234

Open
CaffeinatedCoder wants to merge 3 commits into
release/2.3.1from
fix/ef-filter-captured-ordering-2-3-1
Open

CaffeinatedCoder wants to merge 3 commits into
release/2.3.1from
fix/ef-filter-captured-ordering-2-3-1

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Closes #225

Stacked on #217 (fix/ef-boxed-ordering-refused-2-3-1): both change HandWrittenOrderingInterceptor's Finder. Merge #217 first, then retarget this PR to release/2.3.1.

Problem

HasQueryFilter(r => r.Rank == Limits.Max()) over a value object whose ordering is its own CompareTo was refused when the model was built, though EF evaluates Limits.Max() in memory by CompareTo. The Finder refused an ordering in a filter without asking whether it runs in memory. But the issue's proposed fix (skip whatever reads nothing of the database) is wrong for half the shapes: measured on SQLite, EF sends the context's values to the database and orders them there when the chain takes a lambda. Limits.OrderByDescending(l => l).First() kept the row ranked 3 where memory keeps 1.

Change

An ordering in a filter is let through when it reads nothing of the database and takes no lambda (OrderingRunsInMemory). New verified fact KB-058, with an executable test on plain EF: a lambda-free chain over captured values becomes one scalar parameter, and a chain with a lambda puts the values in the SQL (a VALUES list of parameters in EF 10). The EF Core README says when a filter may order the context's values (claim E05-query-filter-own-values); there's a changelog line under 2.3.1.

Tests

  • Allowed, with the answer memory gives: Max(), Min(), Order().First(), OrderDescending().First() over an array and a list.
  • Refused: OrderByDescending(l => l).First(), MaxBy(l => l), Select(l => (object)l).Max() (which EF happens to evaluate, but the lambda rule keeps it refused, the loud side), and an ordering of the table itself (Set<T>().Max(...)).
  • Reverting the source fails the 4 allowed rows. Seeding the issue's original change (no lambda condition) fails the 3 refused ones.
  • EF Core tests green (24,266 passed, SQL Server on azure-sql-edge), conventions green, KB058 green.

🤖 Generated with Claude Code

HasQueryFilter(r => r.Rank == Limits.Max()) over a value object whose
ordering is its own CompareTo was refused when the model was built,
though EF evaluates Limits.Max() in memory, by CompareTo, and sends the
answer as one parameter. The Finder refused an ordering operator in a
filter without asking whether it runs in memory, as its comparison
branches do.

It now lets an ordering through in a filter when it reads nothing of the
database and takes no lambda. The lambda matters: measured on SQLite,
EF evaluates Limits.Max(), Min(), Order().First() and OrderDescending()
.First() in memory, but sends the values to the database and orders them
there for OrderByDescending(l => l).First(), which kept the row ranked 3
where memory keeps 1, and fails to translate MaxBy(l => l). So #225's
proposed change, skipping by database reads alone, would have answered
those silently; they stay refused. KB-058 pins the distinction with plain
EF (a collection with a lambda goes into the SQL as a VALUES list of
parameters; Max(v => v) throws inside EF). The README says when a filter
may order the context's values (claim E05-query-filter-own-values).

Input class (EFT): an ordering operator in a query filter, by its source:
the context's values without a lambda (allowed: Max, Min, Order().First(),
OrderDescending().First() over an array and a list), the context's values
with a lambda (refused: OrderByDescending(l => l), MaxBy, a boxing
Select before Max, which EF happens to evaluate but which the lambda rule
keeps refused), the entity's navigation and the table itself (refused).

Reverting the source fails the 4 allowed rows; dropping the lambda
condition, the change #225 proposed, fails the 3 refused ones.

Refs #225

Defect-Class: EFT
Found-By: OR
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CaffeinatedCoder CaffeinatedCoder added this to the 2.3.1 milestone Oct 31, 2026
@CaffeinatedCoder CaffeinatedCoder added bug Something isn't working stacked Base is another open PR; merge in order, CI does not run until it targets a release severity/medium Inconsistency or failure in a narrow scenario package/efcore Affects CodoMetis.TypeKit.EntityFrameworkCore area/value-objects Value-object contracts and what the aspects generate for them labels Oct 31, 2026
Base automatically changed from fix/ef-boxed-ordering-refused-2-3-1 to release/2.3.1 October 31, 2026 07:48
@CaffeinatedCoder CaffeinatedCoder removed the stacked Base is another open PR; merge in order, CI does not run until it targets a release label Oct 31, 2026
CaffeinatedCoder and others added 2 commits October 31, 2026 08:59
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r-captured-ordering-2-3-1

# Conflicts:
#	CHANGELOG.md
#	design/knowledge/README.md

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/value-objects Value-object contracts and what the aspects generate for them bug Something isn't working package/efcore Affects CodoMetis.TypeKit.EntityFrameworkCore severity/medium Inconsistency or failure in a narrow scenario

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant