You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
UseOptimisticConcurrency(true) and UseNumericRevisions(true) are not symmetric in how they clear the other flavor's metadata, so the order the two fluent calls are made in decides whether the configuration silently works or throws at bootstrap.
Behavior today (master, 77efabc0f)
// Order A — last-wins, works.opts.Schema.For<Foo>().UseOptimisticConcurrency(true);opts.Schema.For<Foo>().UseNumericRevisions(true);// Order B — InvalidDocumentException at bootstrap.opts.Schema.For<Foo>().UseNumericRevisions(true);opts.Schema.For<Foo>().UseOptimisticConcurrency(true);
publicDocumentMappingExpression<T>UseOptimisticConcurrency(boolenabled){_builder.Alter= m =>{m.UseOptimisticConcurrency=enabled;if(enabled){m.UseNumericRevisions=false;m.Metadata.Version.Enabled=true;// NOTE: does NOT clear m.Metadata.Revision.Enabled}};returnthis;}publicDocumentMappingExpression<T>UseNumericRevisions(boolenabled){_builder.Alter= m =>{m.UseNumericRevisions=enabled;if(enabled){m.UseOptimisticConcurrency=false;m.Metadata.Revision.Enabled=true;m.Metadata.Version.Enabled=false;// <-- clears the other flavor}};returnthis;}
UseNumericRevisions clears Metadata.Version.Enabled; UseOptimisticConcurrency never clears Metadata.Revision.Enabled. Both flavors then read as enabled, and #5121's new guard in DocumentMapping.CompileAndValidate throws.
Note that VersionedPolicy.Apply already reconciles the pair correctly:
So the invalid state is only reachable through a fluent Alter, which runs after the policies.
Why this matters beyond call ordering
The same asymmetry means UseOptimisticConcurrency(true) throws on any type that already has numeric revisions from a policy rather than from an explicit call — it implements IRevisioned/ILongVersioned, carries a [Version] member, or is an aggregate-projection target. In those cases nothing in the user's configuration says "numeric revisions", so the error reads as though Marten invented a conflict. (Covered by interface_driven_revisions_plus_optimistic_concurrency_fails_fast in #5157.)
Proposed direction
Two changes that compose:
Make UseOptimisticConcurrency(true) clear Metadata.Revision.Enabled, mirroring UseNumericRevisions. Both orders then become last-wins for ordinary documents, which is what the fluent API reads like it should do.
Worth deciding explicitly whether (1) should also apply to interface-driven revisions (IRevisioned + an explicit UseOptimisticConcurrency(true)), or whether that should keep failing fast — the interface's Version member would be left unmapped, so continuing to throw there is defensible.
Context
Not a regression: before #5121 both orders reached the database as DDL with two mt_version columns and failed with MartenSchemaException: DDL Execution for 'All Configured Changes' Failed!. Verified against master at de63fc5ed. #5121 turned that into an actionable InvalidDocumentException; this issue is about the underlying asymmetry it surfaced.
Raised while reviewing #5121, deliberately left out of #5157 because it changes document-configuration behavior.
Summary
UseOptimisticConcurrency(true)andUseNumericRevisions(true)are not symmetric in how they clear the other flavor's metadata, so the order the two fluent calls are made in decides whether the configuration silently works or throws at bootstrap.Behavior today (master,
77efabc0f)Root cause
MartenRegistry.DocumentMappingExpression<T>(src/Marten/MartenRegistry.cs:708-742):UseNumericRevisionsclearsMetadata.Version.Enabled;UseOptimisticConcurrencynever clearsMetadata.Revision.Enabled. Both flavors then read as enabled, and #5121's new guard inDocumentMapping.CompileAndValidatethrows.Note that
VersionedPolicy.Applyalready reconciles the pair correctly:So the invalid state is only reachable through a fluent
Alter, which runs after the policies.Why this matters beyond call ordering
The same asymmetry means
UseOptimisticConcurrency(true)throws on any type that already has numeric revisions from a policy rather than from an explicit call — it implementsIRevisioned/ILongVersioned, carries a[Version]member, or is an aggregate-projection target. In those cases nothing in the user's configuration says "numeric revisions", so the error reads as though Marten invented a conflict. (Covered byinterface_driven_revisions_plus_optimistic_concurrency_fails_fastin #5157.)Proposed direction
Two changes that compose:
UseOptimisticConcurrency(true)clearMetadata.Revision.Enabled, mirroringUseNumericRevisions. Both orders then become last-wins for ordinary documents, which is what the fluent API reads like it should do.StoreOptions.Projections.TryFindAggregate(DocumentType, out _). That is the actual Do not allow projected documents to be configured as requiring optimistic concurrency #2978 rule, and it keeps the actionable error where it belongs while letting plain documents override freely.Worth deciding explicitly whether (1) should also apply to interface-driven revisions (
IRevisioned+ an explicitUseOptimisticConcurrency(true)), or whether that should keep failing fast — the interface'sVersionmember would be left unmapped, so continuing to throw there is defensible.Context
Not a regression: before #5121 both orders reached the database as DDL with two
mt_versioncolumns and failed withMartenSchemaException: DDL Execution for 'All Configured Changes' Failed!. Verified against master atde63fc5ed. #5121 turned that into an actionableInvalidDocumentException; this issue is about the underlying asymmetry it surfaced.Raised while reviewing #5121, deliberately left out of #5157 because it changes document-configuration behavior.