dotnet/efcore · error · InvalidOperationException

The optional complex property

Error message

The optional complex property '{type}.{property}' is mapped to columns by flattening the contained properties into its container's table; this mapping requires at least one required property - to allow distinguishing between 'null' and empty values - but the complex type contains only optional properties. Configure the property with a shadow discriminator by adding a call to 'HasDiscriminator()' on the complex property configuration, or map this complex property to a JSON column instead.

What it means

ValidatePropertyMapping throws ComplexPropertyOptionalTableSharing when an optional (nullable) complex property is mapped by flattening its contained properties into the container's table, but every contained property is also optional. Without at least one required property, EF cannot distinguish a null complex instance from an all-null but present instance. The message prescribes HasDiscriminator() on the complex property or mapping to JSON instead.

Solutions

  1. Add a required sentinel property and call HasDiscriminator() on the complex property configuration.
  2. Map the complex property to a JSON column via ToJson(), which can represent null vs empty natively.
  3. Make at least one contained property required (e.g. a non-nullable IsPresent flag).

Example fix

// before — all address fields nullable, complex prop optional
modelBuilder.Entity<Customer>()
    .OwnsOne(c => c.Address, a =>
    {
        a.Property(p => p.Street).IsRequired(false);
        a.Property(p => p.City).IsRequired(false);
    }); // throws

// after — map to JSON so null vs empty is unambiguous
modelBuilder.Entity<Customer>()
    .OwnsOne(c => c.Address, a => a.ToJson("address"));
Defensive patterns

Strategy: validation

Validate before calling

if (!complexProperty.ComplexType.IsMappedToJson()
    && complexProperty.IsNullable
    && complexProperty.ComplexType.GetProperties().All(m => m.IsNullable))
    throw new InvalidOperationException("Optional complex type with all-optional properties needs a discriminator or JSON mapping.");

Type guard

static bool NeedsDiscriminatorOrJson(IComplexProperty cp)
    => !cp.ComplexType.IsMappedToJson() && cp.IsNullable
       && cp.ComplexType.GetProperties().All(m => m.IsNullable);

Prevention

When it happens

Trigger: Configuring OwnsOne with IsRequired(false) where the complex type contains only nullable properties and no JSON mapping. The validator checks `!IsMappedToJson && IsNullable && ComplexType.GetProperties().All(m => m.IsNullable)`.

Common situations: Address-like value objects whose every field (Street, City, Zip) is nullable, configured as optional; refactoring from JSON back to column flattening and losing the discriminator; using IsRequired(false) globally on complex types.

Related errors


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

Appendix: source

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

    /// <inheritdoc />
    protected override void ValidatePropertyMapping(
        IComplexProperty complexProperty,
        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
    {
        base.ValidatePropertyMapping(complexProperty, logger);

        if (complexProperty.IsCollection && !complexProperty.ComplexType.IsMappedToJson())
        {
            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(

View on GitHub (pinned to 3a2006ef56)