Generate TimestampAttribute when scaffolding row version properties - #38795
Open
GOVINSAGA wants to merge 1 commit into
Open
Generate TimestampAttribute when scaffolding row version properties#38795GOVINSAGA wants to merge 1 commit into
GOVINSAGA wants to merge 1 commit into
Conversation
- Emit [Timestamp] from GetDataAnnotations for concurrency-token properties with ValueGenerated.OnAddOrUpdate - Mark the IsRowVersion and IsConcurrencyToken fluent calls as handled by data annotations so they are omitted in data-annotations mode - Share a single IsRowVersion predicate between both so the emit and suppress conditions cannot drift Fixes dotnet#34609
There was a problem hiding this comment.
Pull request overview
Updates EF Core design-time scaffolding so SQL Server rowversion/timestamp properties generate a [Timestamp] data annotation when --data-annotations is used, and ensures the corresponding fluent API calls are suppressed in that mode while remaining unchanged in fluent-only generation.
Changes:
- Emit
[Timestamp]for properties detected as row-version (concurrency token +ValueGenerated.OnAddOrUpdate). - Mark the generated
.IsRowVersion()and.IsConcurrencyToken()fluent fragments as handled-by-data-annotations for row-version properties, so they’re filtered out whenUseDataAnnotationsis enabled. - Add regression tests covering data-annotation emission + fluent suppression, plus guard tests to ensure fluent mode and non-rowversion concurrency tokens remain unaffected.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs | Adds a test asserting [Timestamp] is emitted on a row-version property and fluent config is omitted in data-annotations mode, with model round-trip verification. |
| test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs | Adds tests ensuring fluent .IsRowVersion() remains in non-data-annotations mode, and that non-rowversion concurrency tokens still generate .IsConcurrencyToken() (and not [Timestamp]). |
| src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs | Implements row-version detection, emits TimestampAttribute, and aligns fluent suppression via IsHandledByDataAnnotations on relevant fragments. |
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
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.
Fixes #34609
Scaffolding a SQL Server
rowversioncolumn with--data-annotationsproduced no[Timestamp]attribute, leaving the configuration as fluent API in the generatedDbContext.Before:
After:
[Timestamp]on the property, both fluent calls omitted.Cause
ScaffoldingModelExtensions.GetDataAnnotations(IProperty, …)had noTimestampAttributecase, so the rowversion facets stayed inGetFluentApiCalls, which emitted.IsRowVersion()and.IsConcurrencyToken()withoutIsHandledByDataAnnotations— meaningCSharpDbContextGeneratornever filtered them out.Change
A single shared predicate drives both halves, so the emit and suppress conditions can't drift:
GetDataAnnotationsyields[Timestamp]when it holds, placed with the other core-model attributes.GetFluentApiCallssetsIsHandledByDataAnnotationson both theValueGeneratedandIsConcurrencyTokenfragments. Both need suppressing —.IsRowVersion()already implies the concurrency token, so the previous output was redundant.IsHandledByDataAnnotationsis only consulted whenUseDataAnnotationsis on, so fluent-mode output is unchanged.Round-trip
TimestampAttributeConventionsets exactlyValueGenerated.OnAddOrUpdate+IsConcurrencyToken(true)— identical toIsRowVersion()— so no model state is lost. The new test asserts this through theTestAsyncmodel callback, which recompiles the generated code and rebuilds the model, rather than only string-matching.No
[Column(TypeName = "rowversion")]interaction: the SQL Server scaffolding mapping forrowversionis inferred, soHasColumnTypeis never set.Tests
Three tests, all run against the full
EFCore.Design.Testssuite (1273 passed, 0 failed), with no baseline changes required:TimestampAttribute_is_generated_for_row_version_property— attribute emitted, both fluent calls absent, model round-trips. Verified failing before the change.IsRowVersion_is_still_generated_when_not_using_data_annotations— fluent mode unaffected.IsConcurrencyToken_is_still_generated_for_non_row_version_property_using_data_annotations— guards against over-suppression. Verified this fails if the predicate is widened toIsConcurrencyTokenalone.Behavioral change
Re-scaffolding an existing database with
--data-annotationsnow produces[Timestamp]where it previously produced two fluent calls. The resulting model is unchanged and scaffolded code is regenerated by definition, but flagging it explicitly per the contributing guidelines.Open questions from the issue thread
Raised in #34609 (comment) and still open — happy to adjust:
byte[], keeping the condition the exact inverse of the fluent call being suppressed. BothTimestampAttributeConventionandIsRowVersion()are type-agnostic, and in-box only SQL Serverrowversionsets theConcurrencyTokenscaffolding annotation. Can add abyte[]guard if you'd prefer to be conservative for third-party providers.[ConcurrencyCheck]— plain concurrency tokens have the same gap; left out to keep this to one behavior change. Can fold in if wanted.