dotnet/efcore · error · InvalidOperationException

'HasDiscriminatorInJsonId' or 'HasRootDiscriminatorInJsonId'

Error message

'HasDiscriminatorInJsonId' or 'HasRootDiscriminatorInJsonId' was called on a non-root entity type '{entityType}'. Discriminator configuration for the JSON 'id' property can only be made on the document root.

What it means

Thrown by ValidateDiscriminatorMappings when a non-document-root entity type directly carries the DiscriminatorInKey annotation. JSON 'id' discriminator configuration (whether the discriminator is embedded in the id) applies to the whole document and must be set on the document root. The check uses FindAnnotation(CosmosAnnotationNames.DiscriminatorInKey) on non-root types.

Source

Thrown at src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs:559

            properties[jsonName] = navigation;
        }
    }

    /// <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 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)

View on GitHub (pinned to dbf9771522)

Solutions

  1. Move HasDiscriminatorInJsonId/HasRootDiscriminatorInJsonId to the root entity type of the hierarchy.
  2. Guard shared config helpers so they only run on document-root types (BaseType == null).
  3. Remove the discriminator-in-id annotation from the derived type.

Example fix

// before
modelBuilder.Entity<Manager>().HasRootDiscriminatorInJsonId(); // Manager derives from Employee

// after
modelBuilder.Entity<Employee>().HasRootDiscriminatorInJsonId();
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:DiscriminatorInKey") is null,
        $"{e.Name} (derived) must not call HasDiscriminatorInJsonId/HasRootDiscriminatorInJsonId");
}

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("HasDiscriminatorInJsonId") || ex.Message.Contains("HasRootDiscriminatorInJsonId"))
{
    logger.LogError(ex, "Discriminator-in-JSON-id config was applied to a non-root entity");
    throw;
}

Prevention

When it happens

Trigger: Calling HasDiscriminatorInJsonId(...) or HasRootDiscriminatorInJsonId(...) on an EntityTypeBuilder whose entity is a derived type (BaseType != null).

Common situations: Applying a generic configuration helper to every entity in a hierarchy; misunderstanding that discriminator-in-id is a document-level setting; calling the root variant on a derived type.

Related errors


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/7a8b9af505bfd15f. Report an issue: GitHub.