dotnet/efcore · error · InvalidOperationException

'HasShadowId' was called on a non-root entity type

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 when HasShadowId is configured on a non-root entity type. The shadow-id flag tells EF to manage a shadow 'id' property, which is a document-root concern because the 'id' field belongs to the whole document. The validator detects the HasShadowId annotation on any type where IsDocumentRoot() is false and throws.

Solutions

  1. Move the HasShadowId call to the document-root entity type.
  2. If using IEntityTypeConfiguration<T>, only invoke it on the root.
  3. Remove the call from derived types; the root manages the shadow id for the hierarchy.

Example fix

// before
modelBuilder.Entity<DerivedOrder>().HasShadowId();

// after
modelBuilder.Entity<Order>().HasShadowId();
Defensive patterns

Strategy: type-guard

Type guard

static bool IsDocumentRoot(Type t, IModel model)
    => model.FindEntityType(t)?.IsDocumentRoot() ?? false;

if (IsDocumentRoot(typeof(Order), modelBuilder.Model))
    modelBuilder.Entity<Order>().HasShadowId();

Prevention

When it happens

Trigger: Calling HasShadowId() on a derived entity (one with a BaseType). Fires during ValidateDiscriminatorMappings.

Common situations: Applying HasShadowId inside a per-derived-type configuration; copying a root configuration onto a derived type; misunderstanding that shadow id is a per-document (root) concern.

Related errors


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

Appendix: 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 3a2006ef56)