Skip to content

Conversation

roji
Copy link
Member

@roji roji commented Aug 23, 2025

Fixes #36628

Description

When transforming JsonQueryExpression to SqlServerOpenJsonExpression, nested JSON objects were always projected out of OPENJSON as nvarchar(max), even if the containing column uses the new JSON data type; this caused subsequent JSON_VALUE() operations to fail, since the RETURNING clause which gets generated only works with the new JSON data type (regardless of this bug, nested JSON objects should always have the same type as the container JSON column).

This bug was missed since we don't test against SQL Server 2025 in CI (where the JSON data type can be exercised), only against SQL Server 2022.

Customer impact

Various queries fail which involve LINQ operators on top of JSON collections (major EF 10 feature), where the collection element itself has nested JSON objects.

How found

Testing by @AndriySvyryd in #36628.

Regression

No

Testing

Tests were already there, but are not currently being executed against SQL Server 2025 in CI.

Risk

Low, the fix is very targeted and affects only this specific scenario.

@roji roji requested a review from artl93 August 23, 2025 19:12
@roji roji requested a review from a team as a code owner August 23, 2025 19:12
@roji roji added the ask-mode label Aug 23, 2025

break;
var containerColumnName = structuralType.GetContainerColumnName();
var containerColumnCandidates = structuralType.ContainingEntityType.GetTableMappings()
Copy link
Member Author

Choose a reason for hiding this comment

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

@AndriySvyryd this is a hack to get the container column (IColumn) containing a given structural type (needed here in order to get its type mapping nvarchar(max) or json).

We can easily get the name (as we do just above). I assumed that GetContainerColumnType() would give me the store type, but that seems to work only when the user explicitly configures the JSON column type via HasColumnType() (otherwise it's null). So I sort of hacked it here, simply getting all columns mapped to the containing entity type, and than matching it by name. Note that in theory an entity can have two columns with the same name (in different tables via entity splitting), so this simple by-name matching doesn't work there and will throw (though this is obviously a bit contrived).

I'm assuming this is related to the lack of a relational model representation of JSON and/or JSON columns. Let me know if I've missed a better way to do this or should reference an issue tracking making this kind of thing better.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Just one thought - should GetContainerColumnType() always return the correct type even if the user hasn't set it explicitly? That would allow me to just call it here and be done with it (regardless of the bigger relational JSON modeling).

Copy link
Member

Choose a reason for hiding this comment

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

#36647, but I don't think it fits in 10, it's more involved than how it sounds

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, we'll keep it as it is, no problem.

@AndriySvyryd AndriySvyryd changed the title Fix transformation from JsonQueryExpression to OPENJSON [rc2] Fix transformation from JsonQueryExpression to OPENJSON Aug 25, 2025
{
[var c] => c,
[] => throw new UnreachableException($"No container column found in relational model for {structuralType.DisplayName()}"),
_ => throw new InvalidOperationException(
Copy link
Member

Choose a reason for hiding this comment

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

This should be UnreachableException too. We validate against it

Copy link
Member Author

@roji roji Aug 28, 2025

Choose a reason for hiding this comment

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

Will change. But to be sure I understand, you mean that we don't allow mapping two properties on the same entity to different columns in different tables (entity splitting) if those columns happen to have the same name (again, on two different tables)? Shouldn't we support that?

That problem here is specifically that because of the lack of proper metadata, we're forced to just lookup by column name (from GetContainerColumnName()) and have no additional context on which table that column is on, etc.

Copy link
Member

Choose a reason for hiding this comment

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

We should allow it, but we don't support that yet. And we need the Relational Model support to allow this in a sensible manner.

Copy link
Member Author

Choose a reason for hiding this comment

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

Understood - thanks.

@roji roji enabled auto-merge (squash) August 28, 2025 06:29
@roji roji merged commit 36d0bd0 into dotnet:release/10.0 Aug 28, 2025
7 checks passed
@roji roji deleted the JsonFixes branch August 28, 2025 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Several JsonQueryJsonTypeSqlServerTest tests fail on SQL Server 2025 due to RETURNING
3 participants