Skip to content

Throw for proxy navigations whose accessors cannot be overridden - #39591

Open
ajcvickers wants to merge 1 commit into
dotnet:mainfrom
ajcvickers:fix-32352-internal-proxy-navigations
Open

ajcvickers wants to merge 1 commit into
dotnet:mainfrom
ajcvickers:fix-32352-internal-proxy-navigations

Conversation

@ajcvickers

Copy link
Copy Markdown
Contributor

Fixes #32352

Castle proxies cannot override members that are not visible to DynamicProxyGenAssembly2, such as internal virtual navigations, unless the declaring assembly has [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]. Model validation only checked that the accessor was virtual, so these navigations passed validation and were then silently never lazy-loaded or change-tracked.

Change

Model validation (ProxyBindingRewriter) now throws InvalidOperationException when:

  • UseLazyLoadingProxies: the getter of a lazy-loaded navigation cannot be overridden.
  • UseChangeTrackingProxies: the setter of a navigation cannot be overridden. This includes collections with { get; internal set; }.

The check uses Castle's own ProxyUtil.IsAccessible, so it follows exactly what the proxy generator can override, including the InternalsVisibleTo case. The message says whether the getter or the setter is the problem and how to fix it.

Not affected, and covered by tests:

  • IgnoreNonVirtualNavigations() and EnableLazyLoading(false) still suppress the check for lazy-loading proxies, and owned navigations are still skipped.
  • Lazy-loading proxies only need the getter, so { get; internal set; } keeps working.
  • Entity types that are themselves inaccessible are skipped here. Castle already rejects them when a proxy is created, and reporting them would blame a property that is already public.

Tests

ProxyAccessibilityTests emits entity types at runtime so that every accessibility level (public, protected, protected internal, internal, private protected, private) can be tested with and without InternalsVisibleTo("DynamicProxyGenAssembly2"), for references and collections. It includes end-to-end tests that a visible internal accessor is really intercepted (lazy-loading loads the navigation, change-tracking detects the change), and one documenting the original symptom with validation suppressed.

test.cmd was run. The only failures were five many-to-many tests (ManyM_DB / Organisations tables missing or already existing) caused by parallel runs sharing a database; they pass when run on their own.

Breaking change

Apps with internal virtual navigations, or { get; internal set; } collections under change-tracking proxies, without the InternalsVisibleTo attribute will now get a model-validation error instead of a silently broken navigation. A docs breaking-change note is being sent to dotnet/EntityFramework.Docs.

Not in this PR

🤖 Generated with Claude Code

Castle proxies cannot override members that are not visible to
DynamicProxyGenAssembly2, such as internal virtual navigations, unless the
declaring assembly has [InternalsVisibleTo("DynamicProxyGenAssembly2")].
Such navigations passed model validation but were silently never
lazy-loaded or change-tracked.

Model validation now throws when the getter (lazy-loading proxies) or the
setter (change-tracking proxies) of a navigation cannot be overridden,
using Castle's own accessibility rules. IgnoreNonVirtualNavigations and
EnableLazyLoading(false) still suppress the check for lazy-loading.
Entity types that are themselves inaccessible are not reported here, since
they already fail when a proxy is created.

Fixes dotnet#32352

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@ajcvickers
ajcvickers requested a review from a team as a code owner November 10, 2026 12:01
Copilot AI balanced review requested due to automatic review settings November 10, 2026 12:01

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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EF Core Inheritance: Profile Field Null on Retrieval Despite of Configured Relationships

2 participants