Skip to content

Fix NRE in AddJsonNavigationBindings when combining DbFunction with OwnsOne/OwnsMany ToJson#37855

Merged
roji merged 5 commits intomainfrom
copilot/fix-ef-core-10-regression
Mar 6, 2026
Merged

Fix NRE in AddJsonNavigationBindings when combining DbFunction with OwnsOne/OwnsMany ToJson#37855
roji merged 5 commits intomainfrom
copilot/fix-ef-core-10-regression

Conversation

Copy link
Contributor

Copilot AI commented Mar 5, 2026

CreateFunctionMapping in RelationalModel never added JSON container columns to StoreFunction, so AddJsonNavigationBindings hit a null deref when resolving the container column via IStoreFunction.FindColumn(). Tables and views already handled this via CreateContainerColumn; functions were simply missing it.

Fixes #37842

Changes

  • RelationalEntityTypeExtensions.cs: GetFunctionName now falls back to the ownership principal's function name for owned types — consistent with GetViewName/GetTableName which already do this via GetDefaultViewName/GetDefaultTableName
  • RelationalModel.cs: Restructured CreateFunctionMapping to check mappedType.GetContainerColumnName() and create the container column when non-null, falling to the property loop otherwise — matching the existing pattern in CreateTableMapping and CreateViewMapping. Also added owned JSON type processing in AddTvfs via a new AddTvfMapping helper method so that TVF-mapped entities (which lack a FunctionName annotation) get function mappings for their owned JSON types, eliminating duplicated mapping registration logic.
  • RelationalModelTest.cs: Add Can_use_relational_model_with_functions_and_json_owned_types using existing Order class with both OwnsOne(DateDetails).ToJson() and OwnsMany(Addresses).ToJson() on a TVF-mapped entity, asserting both JSON container and non-JSON columns to ensure no extra columns are added

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…g DbFunction with OwnsOne/OwnsMany ToJson

Add JSON container column creation to CreateFunctionMapping in RelationalModel,
matching how tables and views already handle JSON container columns.
Pass IRelationalTypeMappingSource through AddMappedFunctions, AddTvfs and
CreateFunctionMapping to enable container column creation.

Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix EF Core 10 regression with OwnsMany and DbFunction Fix NRE in AddJsonNavigationBindings when combining DbFunction with OwnsOne/OwnsMany ToJson Mar 5, 2026
Copilot finished work on behalf of roji March 5, 2026 07:16
@roji
Copy link
Member

roji commented Mar 5, 2026

@copilot what was the exact cause of the regression from 9.0 to 10.0?

Copy link
Contributor Author

Copilot AI commented Mar 5, 2026

@copilot what was the exact cause of the regression from 9.0 to 10.0?

In EF Core 9, AddJsonNavigationBindings looked up the JSON container column from the entity's table or view mappings (not the function mapping):

// EF Core 9
var containerColumn = (entityType.GetViewOrTableMappings().SingleOrDefault()?.Table
        ?? entityType.GetDefaultMappings().Single().Table)
    .FindColumn(containerColumnName)!;

So even when querying through a TVF, the container column was always resolved from the regular Table metadata (which had it), and only the tableAlias parameter was used for the column expression.

In EF Core 10, this was refactored to look up the container column via the tableMap dictionary — which maps the actual ITableBase in the query to its alias:

// EF Core 10
var (containerColumn, tableAlias) = tableMap
    .Select(kvp => (Column: kvp.Key.FindColumn(containerColumnName), TableAlias: kvp.Value))
    .SingleOrDefault(c => c.Column is not null);

For TVFs, the tableMap contains the IStoreFunction (not the Table). Since CreateFunctionMapping in RelationalModel.cs never added JSON container columns to StoreFunction (unlike CreateTableMapping which calls CreateContainerColumn), FindColumn() returned null → NRE.

The fix adds the missing CreateContainerColumn call to CreateFunctionMapping so the StoreFunction has the JSON container column, matching the existing table/view pattern.

Copilot finished work on behalf of roji March 5, 2026 08:57
@roji
Copy link
Member

roji commented Mar 5, 2026

@AndriySvyryd this looks like a relational model (owned) JSON issue, triggered by a query change I did in 10.0. Assigning this and the issue to you for now, but let's discuss if you think query should be doing something else.

@roji roji assigned AndriySvyryd and unassigned roji Mar 5, 2026
…nctionName ownership fallback

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
{
var modelBuilder = CreateConventionModelBuilder();

modelBuilder.Entity<EntityWithJsonOwnedType>(cb =>
Copy link
Member

Choose a reason for hiding this comment

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

@copilot Try to use existing classes instead of adding EntityWithJsonOwnedType.
Also assert the non-json columns to ensure no extra ones are addded

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 4bfd2db. Replaced EntityWithJsonOwnedType with existing Order class using OwnsOne(c => c.DateDetails, o => o.ToJson("date_details")), removed the three custom classes and GetEntitiesWithJsonOwnedType method, and added column name assertions to verify no extra columns are present.

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copy link

Copilot AI left a comment

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 NullReferenceException when building relational models that combine table-valued DbFunctions with owned JSON types (OwnsOne/OwnsMany + ToJson), ensuring function mappings include the necessary JSON container columns and owned-type mappings.

Changes:

  • Pass IRelationalTypeMappingSource through function-mapping code paths and create function container columns for JSON-mapped types.
  • Update GetFunctionName to resolve function names from owners for owned (including JSON-mapped) entity types.
  • Extend TVF mapping to include owned JSON entity types and add a regression test covering the scenario.

Reviewed changes

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

File Description
test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs Adds a regression test ensuring TVFs include owned JSON container columns and don’t introduce extra columns.
src/EFCore.Relational/Metadata/Internal/RelationalModel.cs Adds type-mapping source to function mapping, creates JSON container columns for functions, and maps owned JSON types for TVFs.
src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs Updates GetFunctionName to inherit function names from owners for owned/JSON-mapped entity types.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +1007 to +1015
if (ownedType.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionMappings)
is not List<FunctionMapping> ownedFunctionMappings)
{
ownedFunctionMappings = [];
ownedType.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionMappings, ownedFunctionMappings);
}

ownedFunctionMappings.Add(ownedFunctionMapping);
((StoreFunction)ownedFunctionMapping.StoreFunction).EntityTypeMappings.Add(ownedFunctionMapping);
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

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

The annotation-list initialization + mapping registration logic is duplicated (similar logic exists for the root entityType earlier in AddTvfs). Consider extracting this into a small helper to keep the mapping registration consistent and reduce copy/paste (e.g., a local function that “gets/creates function mappings list, adds mapping, and registers it on the StoreFunction”).

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

@copilot The annotation-list initialization + mapping registration logic is duplicated (similar logic exists for the root entityType earlier in AddTvfs). Consider extracting this into a small helper to keep the mapping registration consistent and reduce duplication

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extracted AddTvfMapping helper in e3ee4f0. The duplicated annotation-list initialization + mapping registration logic in AddTvfs is now a single method called for both the root entity and each owned JSON type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extracted AddTvfMapping helper in e3ee4f0.

Comment on lines +1007 to +1015
if (ownedType.FindRuntimeAnnotationValue(RelationalAnnotationNames.FunctionMappings)
is not List<FunctionMapping> ownedFunctionMappings)
{
ownedFunctionMappings = [];
ownedType.AddRuntimeAnnotation(RelationalAnnotationNames.FunctionMappings, ownedFunctionMappings);
}

ownedFunctionMappings.Add(ownedFunctionMapping);
((StoreFunction)ownedFunctionMapping.StoreFunction).EntityTypeMappings.Add(ownedFunctionMapping);
Copy link
Member

Choose a reason for hiding this comment

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

@copilot The annotation-list initialization + mapping registration logic is duplicated (similar logic exists for the root entityType earlier in AddTvfs). Consider extracting this into a small helper to keep the mapping registration consistent and reduce duplication

public string Value { get; set; }
public int Number { get; set; }
}

Copy link
Member

Choose a reason for hiding this comment

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

@copilot Remove blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in e3ee4f0.

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
@AndriySvyryd AndriySvyryd marked this pull request as ready for review March 5, 2026 22:00
@AndriySvyryd AndriySvyryd requested a review from a team as a code owner March 5, 2026 22:00
Copilot AI review requested due to automatic review settings March 5, 2026 22:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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


You can also share your feedback on Copilot code review. Take the survey.

@roji roji merged commit 1b36259 into main Mar 6, 2026
17 checks passed
@roji roji deleted the copilot/fix-ef-core-10-regression branch March 6, 2026 13:15
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 10 regression: owned JSON combined with DbFunction

4 participants