Repository navigation
fix: a value object's hand-written operator is refused in an EF Core query - #189
Merged
CaffeinatedCoder merged 6 commits intoOct 31, 2026
Merged
Conversation
…query EF translates an operator node as the database's arithmetic over the columns and never calls the operator. A hand-written `+` that rounds to cents ran as the plain sum: `Price + Tax > 1.006` over 1.005 and 0.001 kept no row where memory keeps one, and the projection read 1.006 where memory reads 1.01 (measured on SQLite with the refusal taken out). The arithmetic aspect now marks every operator it generates `[TranslatedAsWrappedArithmetic]`. An interceptor, and a finalizing convention for query filters, refuse any other arithmetic, bitwise or shift operator a value object declares, naming the value object and `.Value`; a filter fails when the model is built, and an operator over the context's own values in a filter stays allowed. Conversion operators are left to EF. Decision 122. The input class is every operator a value object can declare: `+`, `*` without an interface, unary `-`, `|`, `~` and a validated value object's `+`, in a filter, a projection, a checked context, `OrderBy`, over an optional operand and a navigation, and in query filters, named or not (`HandWrittenArithmeticTests`). Closes #187 Defect-Class: EFT Found-By: SF Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 30, 2026
Closed
…operator-refused # Conflicts: # design/aspects.md
…e query C# compiles `a && b` over a value object with `operator true` and `false` to its own node, AndAlso, whose method is the value object's `&`, and `||` to OrElse over its `|`. The guard of decision 122 knew And and Or only, so `Switch && Switch` over a column passed: EF read the column where the operators answer false, and the query returned true, in a projection and in a filter alike (found in an outside review of #189, reproduced on SQLite). AndAlso and OrElse join the refused nodes, so both are refused naming the value object and `.Value`, and a query filter with one fails when the model is built. Input class: the expression nodes a value object's hand-written operator can reach a query as. Measured the rest of it: a condition over `operator true` is a call of op_True, no operator node, which EF runs in memory as written; that stays allowed and is held by a test. HandWrittenArithmeticTests gains `&&` and `||` in a projection and a filter, and `||` in a query filter. Reverting the two entries fails all five. Defect-Class: EFT Found-By: OR Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to fix/ef-hand-written-operator-refused # Conflicts: # design/adr/README.md
… 0123 2.3.1 took 0121, so the 2.4.0 records move up by one: the arithmetic record is 0122 (merged from feat/value-object-arithmetic) and this one 0123. Its heading, its index row, and the citations in design/aspects.md and design/ef-core.md follow, and its own citations of the arithmetic record move from 121 to 122. Until 2.3.1's 0121 reaches release/2.4.0, The_records_are_numbered_without_a_gap fails on this branch, the only conventions test that does. Refs #187 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CaffeinatedCoder
added a commit
that referenced
this pull request
Oct 30, 2026
2.3.1 takes decision 0121 (#198), and the 2.4.0 records move up one: the arithmetic record is 0122 (#188), the hand-written-operator record 0123 (#189), and this one 0124. Its heading and index row move, its citations of 121 and 122 become 122 and 123, the arithmetic record's index row names it as refining, and design/ef-core.md links it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erator-refused The base took release/2.4.0 and with it 2.3.1. Three things to resolve: - design/adr/README.md: 0121, 0122, 0123 in order. - HandWrittenOrderingInterceptor: this branch moved DatabaseReads to its own file; 2.3.1 added LambdaFinder beside it (#234) and left DatabaseReads' summary stacked on LambdaFinder's. LambdaFinder stays nested with its own summary; DatabaseReads, unchanged by 2.3.1, stays in DatabaseReads.cs. - CHANGELOG.md: git put this branch's 2.4.0 Fixed entry at the end of the dated 2.3.1 section (ChangelogTests caught its blob/main link). It is back under 2.4.0, and the 2.3.1 section is release/2.4.0's line for line. EntityFrameworkCore.Tests 24289 passed, Conventions.Tests 2045/2045. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 31, 2026
Base automatically changed from
feat/value-object-arithmetic
to
release/2.4.0
October 31, 2026 17:45
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 #187
Stacked on #188 (
feat/value-object-arithmetic); the translation (#190) stacks on this one.Problem
EF translates an operator node as the database's arithmetic over the columns and never calls the operator's method. A hand-written operator therefore ran as the database's arithmetic, not as written. With a
+that rounds to cents, measured on SQLite (EF Core 10.0.12) with the refusal taken out, over 1.005 and 0.001:Price + Tax > 1.006becameef_compare(ef_add("l"."Price", "l"."Tax"), @limit) > 0and kept no row, where memory keeps the one.Select(l => l.Price + l.Tax)read 1.006, where memory reads 1.01.Decision 122 keeps such an operator as a seam, so the build can't stop it. The query has to.
Change
TranslatedAsWrappedArithmeticAttributeis new,[EditorBrowsable(Never)], inCodoMetis.TypeKit.CompilerServices.ValueObjectArithmeticAspectputs it on every operator it generates, viaCodeAnnotations.TranslatedAsWrappedArithmetic.ValueObjectArithmetic(EF) holds anIQueryExpressionInterceptor, and a finalizing convention for query filters, named or not..Value.DatabaseReadsmoves out ofHandWrittenOrderingand is now shared with the new convention.docs/value-objects.md, the EF bullet.design/ef-core.md,aspects.mdandpackages.md.C28-ef-own-operator-*,C28-generated-operators-marked,E18-own-operator-*).Tests
HandWrittenArithmeticTestscovers the input class, every operator a value object can declare:+,*without an interface, unary-,|,~and a validated value object's+;OrderBy, over an optional operand and over a navigation;-beside a hand-written+, arithmetic on.Value, and an operator over captured values, in a query and in a filter.ArithmeticTests.A_generated_operator_is_marked_…: every generatedProbeMoneyoperator carries the mark, and no hand-written one does.Weaving review
MethodInfoandGeneratedValueObjectAttribute, never by name.QueryCompilationStarting, filters at model finalizing, navigations included. Decision 57's ordering refusal is untouched.GetCustomAttribute, and the package still buildsIsAotCompatible.Review fix (41f0155)
An outside review found that
&&and||bypassed the refusal: over a value object withoperator trueandfalsethey areAndAlsoandOrElsenodes whose method is its&or|, andSwitch && Switchreturned true where the operators answer false. Both are refused now.HandWrittenArithmeticTestsgains them in a projection and a filter, and||in a query filter; reverting the two entries fails all five. A condition overoperator truecompiles to a call ofop_True, which EF runs in memory as written; that stays allowed and is tested. EF suite 20,165 passed, 915 skipped, on SQLite, PostgreSQL and Azure SQL Edge.🤖 Generated with Claude Code