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
- Add a required sentinel property and call HasDiscriminator() on the complex property configuration.
- Map the complex property to a JSON column via ToJson(), which can represent null vs empty natively.
- 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
- Add HasDiscriminator() on optional complex properties whose members are all nullable.
- Prefer ToJson() for optional complex types when null vs empty must be distinguishable.
- Make at least one contained property required to act as a sentinel.
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
- Complex property ' ' cannot have both a JSON column name ('…
- Complex property ' ' cannot use 'HasJsonPropertyName()'…
- Complex property ' ' is mapped to JSON but its containing…
- Entity type ' ' is an optional dependent using table…
- 'HasDiscriminatorInJsonId' or…
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)