dotnet/efcore · error · InvalidOperationException
'HasShadowId' was called on a non-root entity type '{entityT
Error message
'HasShadowId' was called on a non-root entity type '{entityType}'. JSON 'id' configuration can only be made on the document root. What it means
Thrown by ValidateDiscriminatorMappings when a non-document-root entity type directly carries the HasShadowId annotation (set via HasShadowId). The __id shadow-property behavior is a document-root concern; derived types inherit the setting. The check uses FindAnnotation(CosmosAnnotationNames.HasShadowId) on non-root types.
Source
Thrown at src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs:565
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected virtual void ValidateDiscriminatorMappings(
IEntityType entityType,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
if (!entityType.IsDocumentRoot()
&& entityType.FindAnnotation(CosmosAnnotationNames.DiscriminatorInKey) != null)
{
throw new InvalidOperationException(CosmosStrings.DiscriminatorInKeyOnNonRoot(entityType.DisplayName()));
}
if (!entityType.IsDocumentRoot()
&& entityType.FindAnnotation(CosmosAnnotationNames.HasShadowId) != null)
{
throw new InvalidOperationException(CosmosStrings.HasShadowIdOnNonRoot(entityType.DisplayName()));
}
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override void ValidateIndex(
IIndex index,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
base.ValidateIndex(index, logger);
if (index.GetVectorIndexType() != null)
{
ValidateVectorIndex(index, logger);View on GitHub (pinned to dbf9771522)
Solutions
- Move the HasShadowId call to the root entity type.
- Prefer the model-level HasShadowIds() on ModelBuilder over per-entity calls in hierarchies.
- Guard shared config with `if (entityType.BaseType == null)` before applying HasShadowId.
Example fix
// before modelBuilder.Entity<Manager>().HasShadowId(); // Manager derives from Employee // after modelBuilder.Entity<Employee>().HasShadowId(); // or model-wide: // modelBuilder.HasShadowIds();
Defensive patterns
Strategy: validation
Validate before calling
using var ctx = new MyContext();
foreach (var e in ctx.Model.GetEntityTypes().Where(t => t.BaseType is not null))
{
Debug.Assert(e.FindAnnotation("Cosmos:HasShadowId") is null,
$"{e.Name} (derived) must not call HasShadowId");
} Type guard
static bool IsDocumentRoot(Microsoft.EntityFrameworkCore.Metadata.IEntityType e) => e.BaseType is null && e.IsDocumentRoot();
Try / catch
try { ctx.Model.GetModel(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("HasShadowId"))
{
logger.LogError(ex, "HasShadowId was applied to a non-root entity");
throw;
} Prevention
- Prefer the model-level modelBuilder.HasShadowIds() over per-entity calls in hierarchies.
- Only call HasShadowId on document roots; guard helpers with a root check.
- Test that no derived type carries the HasShadowId annotation.
When it happens
Trigger: Calling HasShadowId(...) on an EntityTypeBuilder whose entity derives from a base entity (a non-root in an inheritance hierarchy).
Common situations: Running a shared 'configure shadow ids' helper across every entity in a hierarchy; legacy EF Core <9 behavior being re-added per entity after upgrade.
Related errors
- 'HasDiscriminatorInJsonId' or 'HasRootDiscriminatorInJsonId'
- Cosmos automatic-indexing configuration was set on entity ty
- A partition key is defined on entity type '{entityType}', wh
- An Azure Cosmos DB container name is defined on entity type
- The time to live for analytical store was configured to '{tt
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/a5776418f61db69c.
Report an issue: GitHub.