Skip to content

Emit the context check for the containing type map in CollectionMapper (fixes #4650) - #4651

Merged
jbogard merged 1 commit into
mainfrom
fix/collection-maxdepth-context-guard
Sep 4, 2026
Merged

jbogard merged 1 commit into
mainfrom
fix/collection-maxdepth-context-guard

Conversation

@jbogard

@jbogard jbogard commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #4650.

CollectionMapper emits the OverMaxDepth check from the containing type map (memberMap.TypeMap) but decided whether to emit CheckContext from the element type map. With a containing map that has MaxDepth > 0 and a plain element map, the generated plan calls context.OverTypeDepth against the default context, which throws:

Context.Items are only available when using a Map overload that takes Action<IMappingOperationOptions>!

The message is misleading — ThrowInvalidMap is shared between Items and CheckDefault, and the real accessor is TypeDepth.

Because collection execution plans are cached per type pair (MapRequest equality ignores MemberMap), the first member map to compile a given collection pair wins the cache for every other call site. Once a map with MaxDepth compiles the plan, unrelated callers reaching it under the default context fail, so the failure depends on warmup order rather than on configuration. This became reachable for many more configurations in 0afaf1e, where self-referential maps started defaulting to MaxDepth = 64.

The fix emits the context check when either the element map or the containing type map requires it, so the guard and the depth check agree. When the containing map only sets PreserveReferences, the extra check is a no-op at runtime — its own plan has already replaced the default context.

Added CollectionMaxDepthContext, which maps the same List<Service> -> IList<ServiceDto> pair from a self-referential parent and a plain parent in both orders. The recursive-first case fails on main with the exception above and passes with this change; the plain-first case passes either way.

Full unit test suite passes (1219 tests, net10.0).

The underlying cache-key issue — member-specific state baked into a type-pair-keyed plan — is not addressed here and is worth a separate look.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wm2m61jXrAs4FheXR5TufH

Copilot AI lite review requested due to automatic review settings September 3, 2026 19:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change directly addresses the documented mismatch with a minimal, targeted fix and includes a regression test that covers the previously intermittent failure mode.

Pull request overview

Fixes an execution-plan generation bug in CollectionMapper where the OverMaxDepth depth check could run without the corresponding CheckContext guard when MaxDepth was required by the containing type map but not by the element type map—leading to intermittent runtime failures depending on execution plan cache warmup order.

Changes:

  • Updated CollectionMapper to emit CheckContext when either the element type map or the containing memberMap.TypeMap requires context.
  • Added a regression test that reproduces the cache-order-dependent failure and verifies both warmup orders succeed.
File summaries
File Description
src/AutoMapper/Mappers/CollectionMapper.cs Aligns CheckContext emission with the same containing type map used by OverMaxDepth, preventing default-context depth checks.
src/UnitTests/Bug/CollectionMaxDepthContext.cs Adds a regression test covering both compilation/warmup orders for a shared collection type-pair plan.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

CollectionMapper emits the OverMaxDepth check from the containing type map
(memberMap.TypeMap) but decided whether to emit CheckContext from the element
type map. When the containing map has MaxDepth set and the element map is
plain, the generated plan calls context.OverTypeDepth against the default
context, which throws "Context.Items are only available when using a Map
overload that takes Action<IMappingOperationOptions>!".

Collection execution plans are cached per type pair and MapRequest equality
ignores MemberMap, so the first member map to compile a given collection pair
wins the cache for every other call site. Once a map with MaxDepth compiles
the plan, unrelated callers reaching it under the default context fail, which
makes the failure depend on warmup order rather than on configuration.

This became reachable for many more configurations when self-referential type
maps started defaulting to MaxDepth 64 in 0afaf1e.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wm2m61jXrAs4FheXR5TufH
@jbogard
jbogard force-pushed the fix/collection-maxdepth-context-guard branch from c5135ab to 179a97b Compare September 3, 2026 21:37
@jbogard
jbogard merged commit 9b500d5 into main Sep 4, 2026
6 checks passed
@jbogard
jbogard deleted the fix/collection-maxdepth-context-guard branch September 4, 2026 14:22
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.

Intermittent "Context.Items are only available..." when a collection plan is shared with a map that has MaxDepth

2 participants