Repository navigation
Let an editor read and write every language at once, and name preview.3 - #4
Merged
Merged
Conversation
In base-language mode the base language is the entity's own columns, so Set(baseCode, ...) throws and GetAllAsync leaves it out. An editor sending every language at once had to switch the request's language to the base one around the save, back again in a finally, and put the base values into the read result itself: the sample's TranslatedText, global per-request state and its most delicate code. ITranslationAccessor<T>.SetAll(byLanguage) queues every language at once, under any language. Base-language values are assigned to the properties and marked, so the save writes them to the columns whatever is current, and never as that language's translation: CaptureValues sees them as on loan from the base language, and ApplyBaseLanguageRules keeps the column in the UPDATE. The mark holds only while the property keeps that value. An entity whose every translatable value came from SetAll may be created in any language. null empties the column. Everything is checked before anything is queued. GetAllAsync(includeBase: true) adds the base language as stored. EF's original values cannot be trusted for it, since a save in another language leaves that language there, so under EF Core the columns are read with a query of their own, by key, around the translation machinery. Without a change tracker the engine answers what each property held before its first serve, or what SetAll assigned. RememberPersisted no longer marks a property with a staged value it does not hold. After SetAll, that made the next save write the base text as the current language's. Outside base-language mode SetAll is Set per language and includeBase adds nothing. Tests on SQLite, the in-memory engine, PostgreSQL and SQL Server; breaking each safeguard fails the tests that pin it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The composition fixes merged in #3 and SetAll / GetAllAsync(includeBase: true) sat under [Unreleased], which says what changed but not what to install it as. They ship as 1.0.0-preview.3: preview.2 is already published and a published version is immutable. The section gains a summary, the Fixed entries an order that follows the story (the nested save first, then the commit, then the two failure-path leaks), and a Known limitations entry for the one case still open: a retrying strategy with another interceptor registered first. [Unreleased] stays at the top, empty, per Keep a Changelog, and the compare links are rewired so the new heading resolves and [Unreleased] spans preview.3...HEAD. The date is the day the release was prepared, not one the tag can confirm, since the tag does not exist yet. Adjust it if v1.0.0-preview.3 is cut on another day. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 gap 3 of
COMPOSITION-GAPS.md, and names the release the composition work ships in:1.0.0-preview.3.Why
In base-language mode the base language is the entity's own columns. So
For(entity).Set(baseCode, …)throws, andGetAllAsync()leaves the base language out. An editor that shows and sends every language at once had to:finally, because localized messages read the current language;That is dotnetboost-sample's
TranslatedText.cs. It is shared per-request state, and the most delicate code in the application.What changes
SetAll(byLanguage):Set;SetAll;nullempties the column;GetAllAsync(includeBase: true)adds the base language as stored. Under EF Core it reads the columns with a query by key. Without a change tracker, it answers what each property held before its first localization, or whatSetAllassigned.SetAllisSetper language andincludeBaseadds nothing, so editor code needn't know the mode.The two traps the gaps notes named:
Base text saved as the current language. Each base value from
SetAllis marked. Both places that would take it for the current language read the mark:CaptureValuesskips it;ApplyBaseLanguageRuleskeeps the column in theUPDATE.The mark only holds while the property keeps that value, so a later direct edit counts as the current language again.
EF's original values. These aren't used: after a save in another language, EF remembers that language as the original value, while the column keeps the base text.
It also fixes an older bug in
RememberPersisted: it marked a property with a staged value the property didn't hold. AfterSetAll, that made the next save write the base text as the current language's.The second commit names the release. Everything under Unreleased, including #3's fixes, moves to
1.0.0-preview.3, with a summary and a Known limitations entry for the case still open. The date is the day the release was prepared; adjust it if the tag is cut another day.API
SetAlland theGetAllAsync(bool, CancellationToken)overload are new members ofITranslationAccessor<T>. Nothing in the repo implements that interface; only an application implementing it itself would notice. Existing calls behave as before.Testing
New tests:
ProviderTests/EfCore/AllLanguagesTests(14 tests, SQLite, base-language mode underar);UnitTests/AllLanguagesTests(5 tests: the no-change-tracker path, as Dapper and MongoDB drive it);AllLanguagesIntegrationTests: an editor round trip on PostgreSQL and SQL Server.Each safeguard is pinned by a test:
RememberPersistedguard, the load-time memory, the create-rule exemption or the column reader each fails the tests for it;SetAllnot yet saved.Results on net8.0 and net10.0:
The skipped rows are Make the save interceptor safe beside other SaveChanges interceptors #3's known retrying-strategy case.
🤖 Generated with Claude Code