dotnet/efcore · error · InvalidOperationException

The JSON property name should only be configured on nested…

Error message

The JSON property name should only be configured on nested owned navigations.

What it means

HasJsonPropertyName configures the JSON property name for an owned navigation. EF Core requires the navigation's PrincipalEntityType to itself be owned (IsOwned()), because JSON property names are only meaningful on nested owned navigations within a JSON column. Configuring it on a top-level or non-owned navigation throws.

Solutions

  1. Ensure the principal entity type is owned (OwnsOne / OwnsMany) before setting the JSON property name.
  2. Move the HasJsonPropertyName call into the nested owned-navigation configuration chain.
  3. Configure the JSON column mapping (ToJson) on the outermost owned navigation first.

Example fix

// before
modelBuilder.Entity<Order>().Navigation(o => o.Details).HasJsonPropertyName("details");
// after
modelBuilder.Entity<Order>().OwnsOne(o => o.Details, nb => nb.ToJson("details").HasJsonPropertyName("details"));
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the principal entity type is owned before configuring JSON property name.
if (!navigationBuilder.Metadata.PrincipalEntityType.IsOwned())
{
    // configure ownership (OwnsOne/OwnsMany) first, or skip HasJsonPropertyName.
    return;
}
navigationBuilder.HasJsonPropertyName(name);

Type guard

static bool IsNestedOwnedNavigation(OwnedNavigationBuilder nb)
    => nb.Metadata.PrincipalEntityType.IsOwned();

Try / catch

try { navigationBuilder.HasJsonPropertyName(name); }
catch (InvalidOperationException ex) when (ex.Message.Contains("JSON property name"))
{ /* principal not owned; configure OwnsOne/ToJson first */ }

Prevention

When it happens

Trigger: Calling navigationBuilder.HasJsonPropertyName(name) where navigationBuilder.Metadata.PrincipalEntityType.IsOwned() returns false — i.e. the containing entity is not an owned type participating in a JSON mapping.

Common situations: Calling HasJsonPropertyName on a top-level entity's navigation instead of a nested owned one; configuring JSON mapping before marking the type as owned; refactoring ownership without revisiting JSON config.

Related errors


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

Appendix: 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 3a2006ef56)