dotnet/efcore · error · InvalidOperationException
The JSON property name should only be configured on nested o
Error message
The JSON property name should only be configured on nested owned navigations.
What it means
OwnedNavigationBuilder.HasJsonPropertyName() may only be called on a NESTED owned navigation — one whose principal entity type is itself owned (RelationalOwnedNavigationBuilderExtensions.cs:132, RelationalStrings.JsonPropertyNameShouldBeConfiguredOnNestedNavigation). On the outermost owned navigation the principal is the owning (non-owned) entity, so configuring a JSON property name there is meaningless; the JSON column name is configured via ToJson instead.
Source
Thrown at src/EFCore.Relational/Extensions/RelationalOwnedNavigationBuilderExtensions.cs:132
return builder;
}
/// <summary>
/// Configures the navigation of an entity mapped to a JSON column, mapping the navigation to a specific JSON property,
/// rather than using the navigation name.
/// </summary>
/// <param name="navigationBuilder">The builder for the navigation being configured.</param>
/// <param name="name">JSON property name to be used.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static OwnedNavigationBuilder HasJsonPropertyName(
this OwnedNavigationBuilder navigationBuilder,
string? name)
{
Check.NullButNotEmpty(name);
if (!navigationBuilder.Metadata.PrincipalEntityType.IsOwned())
{
throw new InvalidOperationException(
RelationalStrings.JsonPropertyNameShouldBeConfiguredOnNestedNavigation);
}
navigationBuilder.Metadata.DeclaringEntityType.SetJsonPropertyName(name);
return navigationBuilder;
}
/// <summary>
/// Configures the navigation of an entity mapped to a JSON column, mapping the navigation to a specific JSON property,
/// rather than using the navigation name.
/// </summary>
/// <param name="navigationBuilder">The builder for the navigation being configured.</param>
/// <param name="name">JSON property name to be used.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static OwnedNavigationBuilder<TSource, TTarget> HasJsonPropertyName<TSource, TTarget>(
this OwnedNavigationBuilder<TSource, TTarget> navigationBuilder,
string? name)View on GitHub (pinned to dbf9771522)
Solutions
- Call HasJsonPropertyName only one level deep — on an OwnedNavigationBuilder obtained from a nested OwnsOne/OwnsMany inside another owned type.
- To name the JSON column itself, use builder.ToJson("columnName") on the outermost owned navigation.
- Guard generic loops: if (!ownedNav.Metadata.PrincipalEntityType.IsOwned()) use ToJson; else use HasJsonPropertyName.
Example fix
// before
modelBuilder.Entity<Customer>().OwnsOne(c => c.Address, a =>
{
a.HasJsonPropertyName("addr"); // throws: Address's principal (Customer) is not owned
});
// after
modelBuilder.Entity<Customer>().OwnsOne(c => c.Address, a =>
{
a.ToJson("Address"); // name the JSON column at the top level
a.OwnsOne(x => x.Geo, g => g.HasJsonPropertyName("geo")); // nested: ok
}); Defensive patterns
Strategy: validation
Validate before calling
// Only call HasJsonPropertyName on nested owned navigations.
Action<OwnedNavigationBuilder<Customer, Address>> configure = a =>
{
a.ToJson("Address"); // top level: name the JSON column
if (a.Metadata.PrincipalEntityType.IsOwned())
{
a.HasJsonPropertyName("..."); // would only be valid if nested
}
}; Type guard
static bool IsNestedOwned(OwnedNavigationBuilder b) => b.Metadata.PrincipalEntityType.IsOwned();
Prevention
- Use ToJson on the outermost owned navigation to name the JSON column.
- Guard generic JSON-property-naming loops with PrincipalEntityType.IsOwned().
- Reserve HasJsonPropertyName for nested owned navigations only.
When it happens
Trigger: Calling builder.HasJsonPropertyName(...) on the OwnedNavigationBuilder returned directly by EntityTypeBuilder.OwnsOne/OwnsMany (the top-level ownership), where PrincipalEntityType is the owner and not owned.
Common situations: Applying a generic JSON-property-naming convention by iterating all owned navigations without skipping the outermost; migrating from a per-navigation column name to JSON mapping and calling HasJsonPropertyName at the wrong level.
Related errors
- The entity type '{entityType}' is owned by the entity type '
- The entity type '{entityType}' is mapped to the container '{
- The partition key property '{property1}' on '{entityType1}'
- The index over properties '{properties}' is declared on owne
- Both properties '{property1}' and '{property2}' on entity ty
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/3495b0ed40208fac.
Report an issue: GitHub.