{"record":{"id":"7a8b9af505bfd15f","repo":"dotnet/efcore","slug":"hasdiscriminatorinjsonid-or-hasrootdiscriminato","errorCode":null,"errorMessage":"'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.","messagePattern":"'HasDiscriminatorInJsonId' or 'HasRootDiscriminatorInJsonId' was called on a non-root entity type '(.+?)'\\. Discriminator configuration for the JSON 'id' property can only be made on the document root\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs","lineNumber":559,"sourceCode":"\n            properties[jsonName] = navigation;\n        }\n    }\n\n    /// <summary>\n    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to\n    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in\n    ///     any release. You should only use it directly in your code with extreme caution and knowing that\n    ///     doing so can result in application failures when updating to a new Entity Framework Core release.\n    /// </summary>\n    protected virtual void ValidateDiscriminatorMappings(\n        IEntityType entityType,\n        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)\n    {\n        if (!entityType.IsDocumentRoot()\n            && entityType.FindAnnotation(CosmosAnnotationNames.DiscriminatorInKey) != null)\n        {\n            throw new InvalidOperationException(CosmosStrings.DiscriminatorInKeyOnNonRoot(entityType.DisplayName()));\n        }\n\n        if (!entityType.IsDocumentRoot()\n            && entityType.FindAnnotation(CosmosAnnotationNames.HasShadowId) != null)\n        {\n            throw new InvalidOperationException(CosmosStrings.HasShadowIdOnNonRoot(entityType.DisplayName()));\n        }\n    }\n\n    /// <summary>\n    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to\n    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in\n    ///     any release. You should only use it directly in your code with extreme caution and knowing that\n    ///     doing so can result in application failures when updating to a new Entity Framework Core release.\n    /// </summary>\n    protected override void ValidateIndex(\n        IIndex index,\n        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)","sourceCodeStart":541,"sourceCodeEnd":577,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs#L541-L577","documentation":"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.","triggerScenarios":"Calling HasDiscriminatorInJsonId(...) or HasRootDiscriminatorInJsonId(...) on an EntityTypeBuilder whose entity is a derived type (BaseType != null).","commonSituations":"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.","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."],"exampleFix":"// before\nmodelBuilder.Entity<Manager>().HasRootDiscriminatorInJsonId(); // Manager derives from Employee\n\n// after\nmodelBuilder.Entity<Employee>().HasRootDiscriminatorInJsonId();","handlingStrategy":"validation","validationCode":"using var ctx = new MyContext();\nforeach (var e in ctx.Model.GetEntityTypes().Where(t => t.BaseType is not null))\n{\n    Debug.Assert(e.FindAnnotation(\"Cosmos:DiscriminatorInKey\") is null,\n        $\"{e.Name} (derived) must not call HasDiscriminatorInJsonId/HasRootDiscriminatorInJsonId\");\n}","typeGuard":"static bool IsDocumentRoot(Microsoft.EntityFrameworkCore.Metadata.IEntityType e) => e.BaseType is null && e.IsDocumentRoot();","tryCatchPattern":"try { ctx.Model.GetModel(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"HasDiscriminatorInJsonId\") || ex.Message.Contains(\"HasRootDiscriminatorInJsonId\"))\n{\n    logger.LogError(ex, \"Discriminator-in-JSON-id config was applied to a non-root entity\");\n    throw;\n}","preventionTips":["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."],"tags":["cosmos","configuration","model-validation","discriminator","json-id","inheritance"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}