Skip to content

Don't reject JSON entities reached only through Include under AsNoTrackingWithIdentityResolution - #38556

Merged
AndriySvyryd merged 1 commit into
dotnet:mainfrom
ajcvickers:fix-json-ntwir-include-35468
Jul 7, 2026
Merged

AndriySvyryd merged 1 commit into
dotnet:mainfrom
ajcvickers:fix-json-ntwir-include-35468

Conversation

@ajcvickers

Copy link
Copy Markdown
Contributor

Fixes #35468.

Include on a collection navigation whose target has JSON-mapped (.ToJson()) owned data threw InvalidOperationException under AsNoTrackingWithIdentityResolution, because Include can't cause the JSON-ordering corruption the check guards against. Narrowed the guard to exempt Include-only paths, fixed a fallout bug where doing so skipped revalidating part of the shaper tree, and added regression tests.

Full explanation

Combining .Include() on a relational collection navigation with .AsNoTrackingWithIdentityResolution() incorrectly threw:

InvalidOperationException: Projecting queryable operations on JSON
collection is not supported for 'NoTrackingWithIdentityResolution'.

whenever the included entity carried JSON-mapped (.ToJson()) owned data. This was a regression from EF Core 8 to 9/10.

JsonCorrectOrderOfEntitiesForChangeTrackerValidator throws to prevent JSON entities from landing in the change tracker out of order when a JSON collection is reordered/filtered by an arbitrary LINQ operator. But Include cannot filter or reorder owned/JSON navigations, so JSON entities reached purely through Include can never trigger that corruption and are always safe. The validator's guard didn't account for this: case JsonProjectionInfo when _insideCollection threw unconditionally as soon as a JSON entity was nested inside any collection shaper, even when it was also inside an Include.

The fix narrows both throwing guards to JsonProjectionInfo when _insideCollection && !_insideInclude, so Include-reached JSON entities/collections fall through to the existing _includedJsonEntityTypes tracking instead of throwing. QueryableJsonProjectionInfo (an explicit LINQ operator applied directly to a JSON collection) still throws unconditionally in all cases, since that shape cannot occur through Include and remains genuinely unsafe.

Suppressing the throw exposed a second issue: IncludeExpression handling used to fall through to base.VisitExtension, which visits both EntityExpression and NavigationExpression - redundantly revisiting NavigationExpression a second time with _insideInclude reset to false, which would re-trigger the very exception being fixed. The fix now visits NavigationExpression once (with _insideInclude true) and explicitly visits EntityExpression before returning, so every JSON-bearing node in the shaper tree - including chained .Include(a).Include(b) chains - is visited exactly once with the correct _insideInclude state.

Test coverage added:

  • Entity_including_collection_with_json_AsNoTrackingWithIdentityResolution: reproduces the reported bug shape (Include on a relational collection navigation whose target carries JSON data).
  • Entity_including_collection_with_json_and_separate_json_projection_AsNoTrackingWithIdentityResolution: combines Include of a JSON-bearing collection with a separate, non-Include projection of the same JSON entity type in the same query, exercising the _includedJsonEntityTypes / _projectedKeyAccessInfos dedup path in Validate().
  • Cosmos overrides documenting that Cosmos already rejects this Include for an unrelated, pre-existing reason (CosmosStrings.NonEmbeddedIncludeNotSupported), confirming this fix doesn't touch Cosmos behavior.

Verified no regression: all ~80 existing *_AsNoTrackingWithIdentityResolution tests that don't use Include continue to throw as before (the genuinely-unsafe cases this validator protects), the full JsonQuerySqlServerTest and JsonQueryCosmosTest suites are green, and the exact repro from the issue (SQLite, OwnsOne(...).ToJson() on a collection element) no longer throws.

Copilot AI review requested due to automatic review settings July 5, 2026 09:35
@ajcvickers
ajcvickers requested a review from a team as a code owner July 5, 2026 09:35

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.

Pull request overview

Fixes a regression where .Include() over a relational collection whose element has JSON-mapped owned data (.ToJson()) incorrectly threw under .AsNoTrackingWithIdentityResolution(). The PR narrows the JSON entity ordering validator so it only rejects truly unsafe JSON projections (i.e., those produced by queryable operators), while treating Include-only JSON paths as safe, and adjusts Include traversal to avoid re-validating subtrees with the wrong “inside include” state.

Changes:

  • Exempt JSON projections reached only via Include from NoTrackingWithIdentityResolution rejection, while keeping explicit queryable JSON operations rejected.
  • Fix IncludeExpression validation traversal to avoid revisiting NavigationExpression with _insideInclude reset.
  • Add regression tests for the Include + JSON + AsNoTrackingWithIdentityResolution scenario, including a combined Include + separate JSON projection case and Cosmos behavior documentation.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs Adjusts JSON ordering validator guards for Include-only paths and fixes Include traversal to preserve correct _insideInclude state.
test/EFCore.Specification.Tests/Query/JsonQueryTestBase.cs Adds cross-provider regression tests covering Include + JSON under AsNoTrackingWithIdentityResolution, including a combined Include + separate projection scenario.
test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs Adds SQL assertion coverage for the new Include + JSON + AsNoTrackingWithIdentityResolution test on SQL Server.
test/EFCore.SqlServer.FunctionalTests/Query/JsonQueryJsonTypeSqlServerTest.cs Adds the same SQL assertion coverage for the JSON-type SQL Server variant.
test/EFCore.Cosmos.FunctionalTests/Query/JsonQueryCosmosTest.cs Overrides new tests to assert Cosmos’ pre-existing NonEmbeddedIncludeNotSupported behavior remains unchanged.

…NoTrackingWithIdentityResolution

Fixes dotnet#35468. Include on a collection navigation whose target has JSON-mapped (.ToJson()) owned data threw InvalidOperationException under AsNoTrackingWithIdentityResolution, because Include can't cause the JSON-ordering corruption the check guards against. Narrowed the guard to exempt Include-only paths, fixed a fallout bug where doing so skipped revalidating part of the shaper tree, and added regression tests.

<details>
<summary>Full explanation</summary>

Combining `.Include()` on a relational collection navigation with `.AsNoTrackingWithIdentityResolution()` incorrectly threw:

    InvalidOperationException: Projecting queryable operations on JSON
    collection is not supported for 'NoTrackingWithIdentityResolution'.

whenever the included entity carried JSON-mapped (`.ToJson()`) owned data. This was a regression from EF Core 8 to 9/10.

`JsonCorrectOrderOfEntitiesForChangeTrackerValidator` throws to prevent JSON entities from landing in the change tracker out of order when a JSON collection is reordered/filtered by an arbitrary LINQ operator. But `Include` cannot filter or reorder owned/JSON navigations, so JSON entities reached purely through `Include` can never trigger that corruption and are always safe. The validator's guard didn't account for this: `case JsonProjectionInfo when _insideCollection` threw unconditionally as soon as a JSON entity was nested inside any collection shaper, even when it was also inside an `Include`.

The fix narrows both throwing guards to `JsonProjectionInfo when _insideCollection && !_insideInclude`, so Include-reached JSON entities/collections fall through to the existing `_includedJsonEntityTypes` tracking instead of throwing. `QueryableJsonProjectionInfo` (an explicit LINQ operator applied directly to a JSON collection) still throws unconditionally in all cases, since that shape cannot occur through `Include` and remains genuinely unsafe.

Suppressing the throw exposed a second issue: `IncludeExpression` handling used to fall through to `base.VisitExtension`, which visits both `EntityExpression` and `NavigationExpression` - redundantly revisiting `NavigationExpression` a second time with `_insideInclude` reset to false, which would re-trigger the very exception being fixed. The fix now visits `NavigationExpression` once (with `_insideInclude` true) and explicitly visits `EntityExpression` before returning, so every JSON-bearing node in the shaper tree - including chained `.Include(a).Include(b)` chains - is visited exactly once with the correct `_insideInclude` state.

Test coverage added:
- `Entity_including_collection_with_json_AsNoTrackingWithIdentityResolution`: reproduces the reported bug shape (Include on a relational collection navigation whose target carries JSON data).
- `Entity_including_collection_with_json_and_separate_json_projection_AsNoTrackingWithIdentityResolution`: combines Include of a JSON-bearing collection with a separate, non-Include projection of the same JSON entity type in the same query, exercising the `_includedJsonEntityTypes` / `_projectedKeyAccessInfos` dedup path in `Validate()`.
- Cosmos overrides documenting that Cosmos already rejects this Include for an unrelated, pre-existing reason (`CosmosStrings.NonEmbeddedIncludeNotSupported`), confirming this fix doesn't touch Cosmos behavior.

Verified no regression: all ~80 existing `*_AsNoTrackingWithIdentityResolution` tests that don't use Include continue to throw as before (the genuinely-unsafe cases this validator protects), the full `JsonQuerySqlServerTest` and `JsonQueryCosmosTest` suites are green, and the exact repro from the issue (SQLite, `OwnsOne(...).ToJson()` on a collection element) no longer throws.

</details>
@ajcvickers
ajcvickers force-pushed the fix-json-ntwir-include-35468 branch from 45acead to ee8a8ef Compare July 5, 2026 10:41
@AndriySvyryd AndriySvyryd self-assigned this Jul 6, 2026
@AndriySvyryd

Copy link
Copy Markdown
Member

Thanks for your contribution!

@AndriySvyryd
AndriySvyryd merged commit c3178f5 into dotnet:main Jul 7, 2026
15 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 8, 2026
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.

Issue with AsNoTrackingWithIdentityResolution and JSON columns after upgrading to EF Core 9

3 participants