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
- Move HasDiscriminatorInJsonId/HasRootDiscriminatorInJsonId to the root entity type of the hierarchy.
- Guard shared config helpers so they only run on document-root types (BaseType == null).
- 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
- Only call HasDiscriminatorInJsonId/HasRootDiscriminatorInJsonId on the document root.
- Guard shared config helpers with a BaseType == null / IsDocumentRoot check.
- Test that no derived type carries the DiscriminatorInKey annotation.
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
- 'HasShadowId' was called on a non-root entity type '{entityT
- 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 entity type '{entityType}' is sharing the container '{co
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/7a8b9af505bfd15f.
Report an issue: GitHub.