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

  1. Call HasJsonPropertyName only one level deep — on an OwnedNavigationBuilder obtained from a nested OwnsOne/OwnsMany inside another owned type.
  2. To name the JSON column itself, use builder.ToJson("columnName") on the outermost owned navigation.
  3. 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

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


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/3495b0ed40208fac. Report an issue: GitHub.