Skip to content

Conversation

@j-d-ha
Copy link
Collaborator

@j-d-ha j-d-ha commented Jan 19, 2026

🚀 Pull Request

📋 Summary

This PR adds comprehensive support for constructor-based mapping in DynamoMapper, enabling the source generator to deserialize DynamoDB items into C# records and classes with parameterized constructors. This is a major enhancement that expands the types of domain models that can be used with DynamoMapper.

Key Features Added

  1. Constructor-Based Deserialization: The generator now analyzes constructors and intelligently selects the best one for deserialization based on parameter-to-property matching.

  2. [DynamoMapperConstructor] Attribute: Allows explicit constructor selection when multiple constructors are available.

  3. Primary Constructor Support: Full support for C# 12+ primary constructors on records and classes.

  4. Init-Only Properties: Support for properties with init accessors that can only be set during object construction.

  5. Hybrid Initialization: Handles models that use both constructor parameters and settable properties, with smart property initialization mode detection.

  6. Comprehensive Diagnostics: Added new compile-time diagnostics (DM0003-DM0007) to guide developers when constructor mapping issues are detected.

Changes by Component

Generator Enhancements:

  • ConstructorSelector: New component that analyzes and selects the best constructor for deserialization
  • MemberAnalyzer: Shared logic for analyzing both properties and constructor parameters
  • Enhanced ModelClassInfo with constructor metadata and initialization tracking
  • Updated Scriban template to generate constructor-based FromItem methods

Runtime API:

  • Added DynamoMapperConstructorAttribute to mark preferred constructors

Testing:

  • Added 11 comprehensive snapshot tests covering various constructor scenarios
  • Tests cover: primary constructors, attributed constructors, init-only properties, hybrid models, and error cases

Documentation:

  • Updated CLAUDE.md and AGENTS.md with xUnit v3 test commands
  • Added constructor-based examples in DynamoMapper.MapperConstructor project

Technical Details

The constructor selection logic prioritizes:

  1. Constructors explicitly marked with [DynamoMapperConstructor]
  2. Primary constructors (for records/classes)
  3. The constructor with the most parameters that can be satisfied by mapped properties

Property initialization modes are determined per-property:

  • ConstructorParameter: Property is set via constructor parameter
  • DirectAssignment: Property is set after construction (regular or init setter)
  • ReadOnly: Property is read-only and only set via constructor

✅ Checklist

  • My changes build cleanly
  • I've added/updated relevant tests (11 new snapshot tests)
  • I've added/updated documentation (CLAUDE.md, AGENTS.md, examples)
  • I've followed the coding style for this project
  • I've tested the changes locally (all tests passing)

🧪 Related Issues or PRs

This PR implements a core Phase 1 feature for DynamoMapper, enabling it to work with a much broader range of domain models beyond simple POCOs with parameterless constructors.


💬 Notes for Reviewers

Areas to Focus On:

  1. Constructor Selection Logic (ConstructorSelector.cs): The algorithm for choosing the best constructor and matching parameters to properties. Pay attention to the name matching logic (exact, case-insensitive, underscore prefix handling).

  2. Property Initialization Modes: The logic in ModelClassInfo.cs that determines whether each property should be set via constructor parameter or direct assignment.

  3. Template Changes (Mapper.scriban): The new FromItem generation logic that handles constructor invocation with parameter mapping.

  4. Diagnostics: The new error messages (DM0003-DM0007) should be clear and actionable for developers.

  5. Test Coverage: Review the snapshot tests in ConstructorVerifyTests.cs to ensure all edge cases are covered.

Known Limitations:

  • Constructor parameters must match property names (case-insensitive, with underscore prefix tolerance)
  • Circular dependencies between constructor parameters are not detected
  • Performance of constructor selection for classes with many constructors has not been profiled

Follow-up Work:

  • Performance profiling of constructor selection logic for classes with many properties
  • Consider caching constructor analysis results more aggressively
  • Potential enhancement: Support for factory methods as an alternative to constructors

j-d-ha added 12 commits January 18, 2026 15:25
…erialization

- Introduced `DynamoMapperConstructorAttribute` to specify a constructor for DynamoDB deserialization.
- Enables better control over deserialization in DynamoMapper.
- Added a new example project `DynamoMapper.MapperConstructor` to demonstrate constructor mapping.
- Configured project dependencies for DynamoMapper runtime and generator.
- Updated solution file to include the new example project.
- Added `RecordMapper` to map `PersonRecord` to and from DynamoDB items.
- Added `ClassWithConstructor` to map `PersonClass` using a constructor for deserialization.
- Added tests for various class and record types with constructors or properties.
  - Included scenarios like primary constructors, read-only properties, init-only properties,
    and multiple constructors.
- Added failure cases for multiple attributed constructors.
- Ensures support for diverse constructor and property setups in DynamoMapper.

fix(diagnostics): add descriptor for multiple attributed constructors

- Introduced `DM0103` diagnostic descriptor for detecting multiple constructors marked with
  `[DynamoMapperConstructor]`.
- Provides clear guidance to maintain a single attributed constructor per type.
…on methods

- Added `ConstructorSelector` to determine suitable constructors for deserialization.
- Introduced `InitializationMethod` to outline property initialization strategies during deserialization.
- Implemented `PropertyInitializationMode` to support constructor and property-based mapping.
- Enhanced property analysis with `MemberAnalyzer` for shared semantic logic.
- Expanded `WellKnownTypeData` with `DynamoMapperConstructorAttribute`.

This commit enables robust constructor-based deserialization in DynamoMapper.
…logic

- Updated XML documentation to enhance clarity and consistency across multiple files.
- Simplified conditional checks in `PropertyMappingCodeRenderer`.
- Refactored `ConstructorSelector` with expanded multiline formatting for improved readability.
- Applied minor naming adjustments and null checks where necessary.
- Improved accessibility checks for property symbols in `AllPropertiesAccessible`.
- Streamlined loop and conditional structures in `ModelClassInfo` and related files.

This commit enhances code maintainability and improves readability without altering functionality.
… selection

- Updated `AllPropertiesAccessible` to account for computed and read-only properties.
- Improved constructor parameter matching for read-only properties during deserialization.
- Refined logic to ignore computed properties that do not require construction population.
- Expanded XML documentation for clear deserialization behavior guidelines.
- Adjusted conditional logic to enhance clarity and deserialization accuracy.
…ML comments

- Removed unused `<param>` XML comments in `PropertyMappingCodeRenderer` and `PropertyInfo`.
- Corrected punctuation in diagnostic messages for consistency.
- Added missing diagnostics to `AnalyzerReleases.Unshipped.md`.
- Added instructions for listing, filtering, and running tests for specific namespaces/classes.
- Documented usage of xUnit v3 filtered test runs with Microsoft.Testing.Platform.
- Included detailed examples for single test execution and method/class-level filters in `CLAUDE.md` and `AGENTS.md`.
…e initialization logic

- Standardized punctuation for diagnostic `MessageFormat` across codebase and test snapshot files.
- Adjusted comments in `ConstructorSelector` to clarify property initialization approach.
- Enhanced initialization method logic to prefer object-initializer assignments for clarity and defaults.
…ection logic

- Extracted property-related operations into dedicated methods for reusability and clarity.
- Centralized constructor selection logic into a standalone method for improved maintainability.
- Improved property analysis with modular functions for property initialization and diagnostics generation.
- Enhanced readability and organization of `CreatePropertyInfos` and `CreateConstructorInfo`.
- Specified .NET SDK version as "10.0.101" for project consistency.
- Added roll-forward policy set to "latestMinor" to allow minor version updates automatically.
@j-d-ha j-d-ha requested a review from ncipollina January 19, 2026 01:59
Copy link
Contributor

@ncipollina ncipollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stopped my review after my comment. Let's discuss.

Copy link
Contributor

@ncipollina ncipollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 Love this solution. One comment to address.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why did you add this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed to make MTP tests work when run through the CLI. I added this along with the agent commands so that coding agents could better run tests without burning tokens.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This how you made it work with dotnet test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!

j-d-ha and others added 2 commits January 19, 2026 08:18
Co-authored-by: Nick Cipollina <[email protected]>
…n in DynamoMapper

- Added examples showcasing constructor-based deserialization in `index.md`.
- Documented constructor selection rules for `FromItem` in `basic-mapping.md`.
- Explained `[DynamoMapperConstructor]` usage in `attributes.md`.
- Updated `how-it-works.md` with object construction approaches during deserialization.
- Revised `phase-1.md` roadmap to include constructor-based deserialization support.
ncipollina
ncipollina previously approved these changes Jan 19, 2026
Copy link
Contributor

@ncipollina ncipollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Ship it!

- Added detailed explanations for constructor mapping rules in `basic-mapping.md`.
- Linked cross-references to constructor mapping rules from `attributes.md` and `how-it-works.md`.
- Documented hybrid constructor and object initializer usage in `basic-mapping.md`.
- Updated examples and added references for constructor-based deserialization scenarios.
Copy link
Contributor

@ncipollina ncipollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM!

@j-d-ha j-d-ha merged commit b736593 into main Jan 19, 2026
3 checks passed
@j-d-ha j-d-ha deleted the feature/add-constructor-support branch January 19, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants