dotnet/efcore · error · InvalidOperationException

Complex property ' ' is mapped to JSON but its containing…

Error message

Complex property '{complexProperty}' is mapped to JSON but its containing type '{containingType}' is not. Map the root complex type to JSON. See https://github.com/dotnet/efcore/issues/36558.

What it means

ValidatePropertyMapping throws NestedComplexPropertyJsonWithTableSharing when a complex property is mapped to JSON (ComplexType.IsMappedToJson() is true) but its declaring type is itself a ComplexType that is NOT mapped to JSON. A JSON complex nested inside a non-JSON complex is ambiguous (the outer one is flattened to columns). Tracked as Issue #36558; the message points developers to map the root complex type to JSON.

Solutions

  1. Map the outermost (root) complex property to JSON with ToJson(), so the entire nested graph is JSON.
  2. Alternatively, remove ToJson from the inner complex property so the whole graph is flattened to columns.
  3. Ensure JSON mapping is consistent up the complex-property chain — either all JSON from the root or none.

Example fix

// before — outer Address flattened, inner Geo mapped to JSON
modelBuilder.Entity<Customer>()
    .OwnsOne(c => c.Address, a =>
    {
        a.OwnsOne(a2 => a2.Geo, g => g.ToJson("geo")); // throws
    });

// after — map the root complex to JSON
modelBuilder.Entity<Customer>()
    .OwnsOne(c => c.Address, a =>
    {
        a.ToJson("address");
        a.OwnsOne(a2 => a2.Geo, g => { /* inherits JSON mapping */ });
    });
Defensive patterns

Strategy: validation

Validate before calling

if (complexProperty.ComplexType.IsMappedToJson()
    && !complexProperty.DeclaringType.IsMappedToJson()
    && complexProperty.DeclaringType is IComplexType)
    throw new InvalidOperationException("Inner complex is JSON but outer complex is not; map the root to JSON.");

Type guard

static bool HasInconsistentNestedJsonMapping(IComplexProperty cp)
    => cp.ComplexType.IsMappedToJson()
       && !cp.DeclaringType.IsMappedToJson()
       && cp.DeclaringType is IComplexType;

Prevention

When it happens

Trigger: A nested complex property where the inner complex is configured with ToJson but the outer (containing) complex is not — i.e. outer flattens to columns while inner wants JSON. The check is `complexProperty.ComplexType.IsMappedToJson() && !DeclaringType.IsMappedToJson() && DeclaringType is IComplexType`.

Common situations: Adding ToJson only on the inner complex of a nested value-object graph; refactoring where the outer ToJson was removed but the inner kept; scaffolding that inconsistently applies JSON mapping across nesting levels.

Related errors


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

Appendix: source

Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:307

                        columnName,
                        complexProperty.GetJsonPropertyName()));
            }

            if (!complexProperty.DeclaringType.IsMappedToJson())
            {
                throw new InvalidOperationException(
                    RelationalStrings.ComplexPropertyJsonPropertyNameWithoutJsonMapping(
                        $"{complexProperty.DeclaringType.DisplayName()}.{complexProperty.Name}"));
            }
        }

        if (complexProperty.ComplexType.IsMappedToJson())
        {
            if (!complexProperty.DeclaringType.IsMappedToJson()
                && complexProperty.DeclaringType is IComplexType)
            {
                // Issue #36558
                throw new InvalidOperationException(
                    RelationalStrings.NestedComplexPropertyJsonWithTableSharing(
                        $"{complexProperty.DeclaringType.DisplayName()}.{complexProperty.Name}",
                        complexProperty.DeclaringType.DisplayName()));
            }

            ValidateJsonProperties(complexProperty.ComplexType);
        }
    }

    /// <summary>
    ///     Validates the SQL query mapping for an entity type.
    /// </summary>
    /// <param name="entityType">The entity type to validate.</param>
    /// <param name="logger">The logger to use.</param>
    protected virtual void ValidateSqlQuery(
        IEntityType entityType,
        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
    {

View on GitHub (pinned to 3a2006ef56)