Unwrap Nullable<T> when determining a Polecat aggregate's id type (GH-3942) - #3948
Merged
Conversation
…-3942) WriteModelAttribute.Modify asks the persistence provider for the aggregate's id type, and FindIdentity branches on whether that answer is primitive. The two stores disagreed for a nullable id property: Marten the configured document id type -- never nullable Polecat sagaType.GetProperty("Id").PropertyType verbatim So `public AlertId? Id { get; set; }` answered Nullable<AlertId> under Polecat. That is not primitive, so IsPrimitiveIdType was false, the documented IdentifiedBy<T> escape hatch was skipped entirely, and the message was scanned for a Nullable<AlertId> member -- which exists in no codebase. Every [WriteModel] chain over such an aggregate failed to build under the Polecat flavour while the identical shared source compiled and ran under Marten. Both reflection sites now unwrap: DetermineSagaIdType and the DocumentExistsAttribute.ResolveIdType that is documented to match it. Nothing else changes -- a non-nullable id, a string id, and a missing Id property all resolve exactly as before. The failure needs both a nullable id property and the Polecat flavour, so it is invisible to a Marten-only run, and it is a codegen-time failure that under TypeLoadMode.Dynamic only surfaces when the chain's message first arrives. The new tests are therefore reflection-level rather than integration: 7 tests, 3 of which fail without this change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014PwvC24c5dNxv7DJR7RUjv
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 #3942.
WriteModelAttribute.Modifyasks the persistence provider for the aggregate's id type, andFindIdentitythen branches on whether that answer is primitive. The two stores disagreed for a nullable id property:IdisAlertId?(a strong-typedrecord struct)stringthere (StreamIdentity.AsString)sagaType.GetProperty("Id").PropertyTypeverbatim →Nullable<AlertId>Nullable<AlertId>is not primitive, soIsPrimitiveIdTypewas false, the documentedIdentifiedBy<T>escape hatch was skipped entirely, and the message was scanned for aNullable<AlertId>member — which exists in no codebase. The result wasInvalidOperationException: Unable to determine an aggregate id, naming neither the type nor nullability.That broke every
[WriteModel]chain over such an aggregate in the Polecat/SQL Server flavour, while the identical shared source compiled and ran fine under Marten.Change
Both reflection sites now unwrap, exactly as the issue suggested:
PolecatPersistenceFrameProvider.DetermineSagaIdTypeDocumentExistsAttribute.ResolveIdType, whose doc comment already declared it matches the former — so they stay in lockstepNothing else changes: a non-nullable id, a string id, and a missing
Idproperty all resolve exactly as before.Verification
New
Bugs/Bug_3942_nullable_aggregate_id_type.cs— 7 tests, 3 of which fail without this change (the two nullable cases on the saga provider, plus the document-resolution lockstep test). The rest pin that the untouched cases stay untouched.These are deliberately reflection-level rather than integration tests. The failure is at codegen time, and under
TypeLoadMode.Dynamica chain only fails when its message first arrives — so an integration test would have to dispatch every message type to see it, which is why it took a codegen sweep across both flavours to surface at all.Note
string?erases tostringat runtime, which is precisely why this only ever bit value-type ids; there is a test pinning that too.🤖 Generated with Claude Code
https://claude.ai/code/session_014PwvC24c5dNxv7DJR7RUjv