Skip to content

Add tests for lazy-loading proxy behavior with detached entities after context disposal#37056

Closed
Copilot wants to merge 7 commits intorelease/10.0from
copilot/document-ef-core-8-issue
Closed

Add tests for lazy-loading proxy behavior with detached entities after context disposal#37056
Copilot wants to merge 7 commits intorelease/10.0from
copilot/document-ef-core-8-issue

Conversation

Copy link
Contributor

Copilot AI commented Oct 31, 2025

User reported nondeterministic CLR hangs in EF Core 8.0.4 when accessing lazy-loaded navigation properties on detached entities after DbContext disposal. Investigation shows current implementation handles this correctly via _detached flag check in LazyLoader.ShouldLoad(), which returns early before accessing disposed context.

Changes

  • Added 4 test methods covering detached entity scenarios:

    • Basic navigation access on detached entity
    • Collection enumeration on detached entity
    • Non-detached entity with disposed context (throws exception, doesn't hang)
    • Complex multi-level navigation graphs
  • Added test infrastructure: Parent/Child/GrandChild entities and ComplexGraphContext for complex graph scenarios

  • Documentation: XML comments explaining each test's purpose and investigation findings

Example Scenario

// Entity is detached before context disposal
using (var scope = serviceProvider.CreateScope())
{
    var context = scope.ServiceProvider.GetService<MyContext>();
    entity = context.Set<MyEntity>().Single();
    context.Entry(entity).State = EntityState.Detached; // Sets _detached = true
}

// Should not hang - ShouldLoad() returns false early
var navigation = entity.NavigationProperty; // Returns null, no hang

All 80 tests pass. No hangs observed in any scenario, indicating current implementation is safe.

Original prompt

This section details on the original issue you should resolve

<issue_title>EF Core 8 lazy-loading proxy caused nondeterministic CLR hang, root cause and resolution</issue_title>
<issue_description>### Question

This isn’t a question, it’s a documentation post describing a resolved issue encountered during a .NET 8 EF Core migration. Posting here so others can find it if they encounter similar nondeterministic hangs or CLR stalls.

clr_corruption_case_study.md

Your code

Stack traces


Verbose output


EF Core version

8.0.4

Database provider

No response

Target framework

.NET 8.0

Operating system

macOS 26.1

IDE

Visual Studio Code 1.105.1</issue_description>

<agent_instructions>First try to create a test that accesses lazy-loaded navigations of types for which lazy-loaded proxies are created after the context was disposed. To match this issue the test should hang instead of throwing ObjectDisposedException or LazyLoadOnDisposedContextWarning. This might happen if the entry in question was detached before the context was disposed, but also explore other scenarios</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@AndriySvyryd Embedding the linked file for ease of reading:

When the CLR Lies: Diagnosing Nondeterministic Metadata Corruption in EF Core 8

Abstract

This case study documents a rare and deeply nondeterministic corruption event in the .NET 8 Common Language Runtime (CLR) triggered by Entity Framework Core 8 navigation proxy enumeration. During the modernization of a production-grade enterprise data-processing system, a long-running background job began freezing unpredictably inside benign control-flow branches. The failure was traced to EF Core’s internal lazy-loading proxy system, which corrupted CLR heap metadata when enumerating navigation properties on detached entities. This paper presents the investigation, root cause analysis, and resolution strategy, culminating in the development of the SafeGetRecords pattern.


1. Context and Environment

System: High-volume transactional processing service (background worker)
Legacy Stack: .NET Framework 4.8 with Entity Framework 6
Migration Target: .NET 8 with EF Core 8
Symptoms: Background job execution hung indefinitely, consuming no CPU, with no exceptions or diagnostic output.
Trigger: Occurred only when processing certain resubmitted transaction groups.

The job functioned correctly for all other inputs, suggesting a nondeterministic runtime corruption rather than a logic error.


2. Initial Observations

  1. Hang consistently occurred at the line:
    if (shouldEnterBranch)
    despite shouldEnterBranch == false.
  2. All entity states were valid, and DbContext appeared intact prior to freeze.
  3. The job remained responsive to logging up until the branch evaluation, then deadlocked silently.

Debugger inspection showed a suspended thread inside the JIT evaluation pipeline — not within EF or SQL operations.


3. Discovery Path

Stage Hypothesis Outcome
1 Mismanaged DbContext disposal Disproved — issue persisted with isolated contexts
2 Recursive logic or infinite loop Disproved — instrumentation confirmed correct flow
3 EF Core metadata corruption Confirmed — JIT evaluation broke after navigation enumeration

The corruption only occurred when an entity’s navigation collection was touched — even with benign calls like .Count() or .ToList().


4. Root Cause: EF Core Proxy Corruption

Trigger Condition: Enumerating a navigation property after the parent DbContext was disposed or under deferred proxy loading.

Mechanism: EF Core 8 creates dynamic lazy-loading proxies through an internal ILazyLoader. When the parent context is disposed, the proxy retains a stale pointer to the StateManager. Any subsequent enumeration attempts to resolve related entities, invoking the loader through a dangling reference.

Result:

  • CLR heap metadata becomes corrupted.
  • JIT compiler attempts to access invalid memory.
  • Execution halts indefinitely on unrelated IL instructions (e.g., if (x) evaluations).

Unlike a normal ObjectDisposedException, this is heap-level corruption, making it appear as nondeterministic logic failure.


5. Verification

Controlled tests were run:

  • Instrumented pre/post navigation property access with timestamps.
  • Replaced navigation access with explicit context queries.
  • Observed that hangs d...

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI self-assigned this Oct 31, 2025
Copilot AI and others added 6 commits October 31, 2025 23:58
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…gation

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot AI changed the title [WIP] Document resolution for EF Core 8 lazy-loading proxy issue Add tests for lazy-loading proxy behavior with detached entities after context disposal Nov 1, 2025
Copilot AI requested a review from AndriySvyryd November 1, 2025 00:21
@AndriySvyryd AndriySvyryd deleted the copilot/document-ef-core-8-issue branch January 6, 2026 21:33
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.

2 participants