dotnet/efcore · error · InvalidOperationException
Entity type ' ' is an optional dependent using table…
Error message
Entity type '{entityType}' is an optional dependent using table sharing and containing other dependents without any required non shared property to identify whether the entity exists. If all nullable properties contain a 'null' value in database then an object instance won't be created in the query causing nested dependent's values to be lost. Add a required property to create instances with 'null' values for other properties or mark the incoming navigation as required to always create an instance. What it means
Thrown by ValidateOptionalDependents when an optional dependent (owned type, nullable navigation) is shared-table-mapped with the principal AND itself has referencing foreign keys whose declaring entity types are also in mappedTypes — i.e. nested owned dependents — yet the optional dependent has no required non-shared identifying column. Without such a column, EF cannot distinguish a null instance from an all-null instance, and would silently lose nested dependents during query, so the validator throws rather than corrupt data.
Solutions
- Add at least one required (non-nullable, non-shared) property to the optional dependent so EF can detect its existence: builder.OwnsOne(e => e.Address, a => { a.Property(x => x.Street).IsRequired(); }).
- Mark the navigation to the dependent as required: builder.OwnsOne(e => e.Address, a => a.Navigation(e => e.Address).IsRequired()) — but then it is no longer optional.
- Restructure so the nested dependents live on a separate table or are not mapped to the same shared table.
Example fix
// before
builder.OwnsOne(e => e.Address, a =>
{
a.ToTable("Customers");
a.Property(x => x.Street); // nullable
a.OwnsOne(x => x.Geo, g => g.Property(p => p.Lat)); // nested dependent
});
// after
builder.OwnsOne(e => e.Address, a =>
{
a.ToTable("Customers");
a.Property(x => x.Street).IsRequired(); // sentinel for existence
a.OwnsOne(x => x.Geo, g => g.Property(p => p.Lat));
}); Defensive patterns
Strategy: validation
Validate before calling
// Walk owned types and warn on optional dependents with nested dependents but no sentinel.
foreach (var et in model.GetEntityTypes())
{
var owns = et.GetReferencingForeignKeys()
.Where(fk => fk.IsOwnership && fk.DeclaringEntityType.IsMappedToTable());
foreach (var fk in owns)
{
var owned = fk.DeclaringEntityType;
bool navigationOptional = fk.Properties.Any(p => p.IsNullable);
bool hasNestedDependent = owned.GetReferencingForeignKeys()
.Any(f => f.IsOwnership);
bool hasRequiredNonShared = owned.GetProperties()
.Any(p => !p.IsNullable && !p.IsShadowProperty());
if (navigationOptional && hasNestedDependent && !hasRequiredNonShared)
Console.WriteLine($"{owned.Name} is an optional dependent with nested dependents and no sentinel property.");
}
} Prevention
- Add an IsRequired() property on any optional owned type that itself owns other types.
- Prefer marking the navigation as required when the dependent must always exist.
- Avoid nesting optional owned types without an identifying non-nullable column.
When it happens
Trigger: An owned entity type configured with .OwnsOne(...) where the navigation is optional (nullable), sharing the principal's table, and that owned type itself has .OwnsOne(...) children mapped to the same table, but every column on the optional dependent is nullable (so all-null is a valid row). The validator's requiredNonSharedColumnFound flag stays false AND GetReferencingForeignKeys().Any(... mappedTypes.Contains) is true -> throw.
Common situations: Optional owned address with optional owned contact-info nested in it, all columns nullable; refactoring an entity to owned without marking a sentinel required column; TPH with optional complex types.
Related errors
- Entity type ' ' has a split mapping and is an optional…
- Entity type ' ' has a split mapping for ' ' that is shared…
- The entity type ' ' is mapped to ' '. However the principal…
- The entity type ' ' is mapped to the container ' ' but it…
- The entity type ' ' is owned by the entity type ' ', but is…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/b9f36265e8d4ba2e.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:1102
continue;
}
var columnName = property.GetColumnName(tableIdentifier);
if (columnName != null)
{
if (!principalColumns.Contains(columnName))
{
requiredNonSharedColumnFound = true;
break;
}
}
}
if (!requiredNonSharedColumnFound)
{
if (entityType.GetReferencingForeignKeys().Select(e => e.DeclaringEntityType).Any(t => mappedTypes.Contains(t)))
{
throw new InvalidOperationException(
RelationalStrings.OptionalDependentWithDependentWithoutIdentifyingProperty(entityType.DisplayName()));
}
logger.OptionalDependentWithoutIdentifyingPropertyWarning(entityType);
}
}
(List<IEntityType> EntityTypes, bool Optional) GetPrincipalEntityTypes(IEntityType entityType)
{
if (!principalEntityTypesMap.TryGetValue(entityType, out var tuple))
{
var list = new List<IEntityType>();
var optional = false;
foreach (var foreignKey in entityType.FindForeignKeys(entityType.FindPrimaryKey()!.Properties))
{
var principalEntityType = foreignKey.PrincipalEntityType;
if (foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
|| !mappedTypes.Contains(principalEntityType))View on GitHub (pinned to 3a2006ef56)