dotnet/efcore · error · InvalidOperationException

Complex property ' ' cannot have both a JSON column name ('…

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 throws ComplexPropertyBothJsonColumnAndJsonPropertyName when a complex property has both a JSON column mapping (the ContainerColumnName annotation, set by ToJson) and an explicit JSON property name (HasJsonPropertyName). These are mutually exclusive: ToJson makes the complex type its own JSON column, while HasJsonPropertyName designates a property within a containing JSON column. The validator rejects the ambiguous combination.

Solutions

  1. Choose one strategy: use ToJson() to map the complex type as its own JSON column, OR use HasJsonPropertyName() to map it inside an existing JSON column — not both.
  2. Remove the conflicting call: drop HasJsonPropertyName when ToJson is present, or drop ToJson when the complex property lives inside another JSON-mapped type.
  3. Audit the configuration chain for stale fluent calls left over from a refactor.

Example fix

// before — both configured
modelBuilder.Entity<Order>()
    .OwnsOne(o => o.Details, d =>
    {
        d.ToJson("details");
        d.HasJsonPropertyName("order_details"); // throws
    });

// after — pick one (ToJson makes Details its own column)
modelBuilder.Entity<Order>()
    .OwnsOne(o => o.Details, d => d.ToJson("details"));
Defensive patterns

Strategy: validation

Validate before calling

var hasColumn = complexProperty.ComplexType
    .FindAnnotation(RelationalAnnotationNames.ContainerColumnName)?.Value is string;
var hasJsonName = complexProperty.GetJsonPropertyName() != null;
if (hasColumn && hasJsonName)
    throw new InvalidOperationException("Complex property cannot have both JSON column and JSON property name.");

Type guard

static bool HasConflictingJsonConfig(IComplexProperty cp)
    => cp.GetJsonPropertyName() != null
       && cp.ComplexType.FindAnnotation(RelationalAnnotationNames.ContainerColumnName)?.Value is string;

Prevention

When it happens

Trigger: Chaining .ToJson("col") and .HasJsonPropertyName("name") on the same complex property builder; or scaffolding that produced both annotations. The check reads ContainerColumnName from the annotation and compares with GetJsonPropertyName() result.

Common situations: Copy-pasting configuration between owned-type-as-JSON and complex-property-as-JSON-property; partial migration where ToJson was added but HasJsonPropertyName was retained; IDE auto-complete adding the wrong fluent call.

Related errors


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

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