dotnet/efcore · error · InvalidOperationException

Complex property '{complexProperty}' cannot have both a JSON

Error message

Complex property '{complexProperty}' cannot have both a JSON column name ('{columnName}') and a JSON property name ('{propertyName}') configured. Use 'ToJson()' to map to a JSON column or 'HasJsonPropertyName()' to map as a JSON property within a containing JSON column, but not both.

What it means

ValidatePropertyMapping (RelationalModelExtensions.cs:282-291) throws when a complex property simultaneously has a JSON column name annotation (ContainerColumnName, set via ToJson/HasContainerColumnName) AND a JSON property name annotation (JsonPropertyName, set via HasJsonPropertyName). These describe mutually exclusive mappings: ToJson maps the property to its own JSON column, HasJsonPropertyName maps it as a key inside an enclosing JSON column. Specifying both is contradictory.

Source

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

        {
            throw new InvalidOperationException(
                RelationalStrings.ComplexCollectionNotMappedToJson(
                    complexProperty.DeclaringType.DisplayName(), complexProperty.Name));
        }

        if (!complexProperty.ComplexType.IsMappedToJson()
            && complexProperty.IsNullable
            && complexProperty.ComplexType.GetProperties().All(m => m.IsNullable))
        {
            throw new InvalidOperationException(
                RelationalStrings.ComplexPropertyOptionalTableSharing(complexProperty.ComplexType.DisplayName(), complexProperty.Name));
        }

        if (complexProperty.GetJsonPropertyName() != null)
        {
            if (complexProperty.ComplexType.FindAnnotation(RelationalAnnotationNames.ContainerColumnName)?.Value is string columnName)
            {
                throw new InvalidOperationException(
                    RelationalStrings.ComplexPropertyBothJsonColumnAndJsonPropertyName(
                        $"{complexProperty.DeclaringType.DisplayName()}.{complexProperty.Name}",
                        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)

View on GitHub (pinned to dbf9771522)

Solutions

  1. If the complex property should be its own JSON column, keep ToJson() and remove HasJsonPropertyName().
  2. If the complex property should be a JSON key inside an owning JSON column, keep HasJsonPropertyName() and remove ToJson()/HasContainerColumnName(), ensuring the containing type is JSON-mapped.
  3. Audit the configuration chain so only one JSON mapping intent is expressed.

Example fix

// before - both mappings
modelBuilder.Entity<Order>().OwnsOne(o => o.Billing, b =>
{
    b.ToJson("BillingJson");
    b.HasJsonPropertyName("bill"); // throws: cannot have both
});

// after - pick one (own JSON column)
modelBuilder.Entity<Order>().OwnsOne(o => o.Billing, b =>
{
    b.ToJson("BillingJson");
});
// OR (nested inside another JSON column)
// b.HasJsonPropertyName("bill");
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in context.Model.GetEntityTypes())
{
    foreach (var cp in et.GetComplexProperties())
    {
        bool hasColumnName = cp.ComplexType
            .FindAnnotation(RelationalAnnotationNames.ContainerColumnName)?.Value is string;
        bool hasJsonProperty = cp.GetJsonPropertyName() != null;
        if (hasColumnName && hasJsonProperty)
        {
            // will throw - keep only one JSON mapping intent.
        }
    }
}

Prevention

When it happens

Trigger: Calling both ToJson("col") (or HasContainerColumnName) and HasJsonPropertyName("key") on the same complex property. Thrown at model validation.

Common situations: Copy-pasting JSON config; refactoring a complex type from a standalone JSON column into a nested property but leaving the old ToJson() call in place.

Related errors


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