dotnet/efcore · error · InvalidOperationException
The optional complex property '{type}.{property}' is mapped
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 (RelationalModelValidator.cs:274-280) throws for an OPTIONAL complex property that is NOT mapped to JSON and whose complex type contains ONLY nullable properties. When such a complex property is flattened into the owner's table, EF needs at least one required column to distinguish 'the whole complex is null' from 'the complex is present but empty/all-null'. With all-optional columns that disambiguation is impossible, so validation rejects it.
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 dbf9771522)
Solutions
- Make at least one property of the complex type required (IsRequired) so it can act as the null sentinel.
- Add a shadow discriminator via HasDiscriminator() on the complex property configuration.
- Map the complex property to a JSON column (ToJson()) instead of flattening, which sidesteps the sentinel requirement.
Example fix
// before - optional complex, all members nullable
modelBuilder.Entity<Customer>().ComplexProperty(c => c.Address, a =>
{
a.Property(p => p.Street).IsRequired(false);
a.Property(p => p.City).IsRequired(false);
}); // Address is nullable, all members nullable -> throws
// after - one required sentinel member
modelBuilder.Entity<Customer>().ComplexProperty(c => c.Address, a =>
{
a.Property(p => p.Street).IsRequired();
});
// OR add a shadow discriminator: a.HasDiscriminator();
// OR a.ToJson("AddressJson"); Defensive patterns
Strategy: validation
Validate before calling
foreach (var et in context.Model.GetEntityTypes())
{
foreach (var cp in et.GetComplexProperties())
{
if (!cp.IsCollection
&& cp.IsNullable
&& !cp.ComplexType.IsMappedToJson()
&& cp.ComplexType.GetProperties().All(p => p.IsNullable))
{
// will throw - make a member required, add HasDiscriminator, or use ToJson().
}
}
} Prevention
- Ensure optional complex types flattened into a table have at least one required member.
- Prefer HasDiscriminator() or ToJson() when all members are legitimately optional.
- Review nullable complex types during model review.
When it happens
Trigger: Configuring an optional (nullable) complex property whose every member property is nullable, with table-sharing (flattened) mapping rather than JSON. Thrown at model validation.
Common situations: Value-object/complex types where all fields are legitimately optional (e.g. an Address with all-optional lines); making a complex property nullable to allow 'no value' but giving EF no sentinel column.
Related errors
- The property '{property}' on type '{type}' is mapped to a JS
- The key {keyProperties} on the entity type '{entityType}' ca
- Complex property '{complexProperty}' cannot have both a JSON
- Complex property '{complexProperty}' cannot use 'HasJsonProp
- Complex property '{complexProperty}' is mapped to JSON but i
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/73ee309ffce01dad.
Report an issue: GitHub.