{"record":{"id":"bfa9571fef3e52ba","repo":"dotnet/efcore","slug":"the-specified-discriminator-value-value-for","errorCode":null,"errorMessage":"The specified discriminator value '{value}' for '{entityType}' is not a string. Configure a string discriminator value instead.","messagePattern":"The specified discriminator value '(.+?)' for '(.+?)' is not a string\\. Configure a string discriminator value instead\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs","lineNumber":2151,"sourceCode":"            var derivedTypes = entityType.GetDerivedTypesInclusive().ToList();\n            var discriminatorValues = new Dictionary<string, IEntityType>();\n            foreach (var derivedType in derivedTypes)\n            {\n                foreach (var complexProperty in derivedType.GetDeclaredComplexProperties())\n                {\n                    ValidateDiscriminatorValues(complexProperty.ComplexType);\n                }\n\n                var discriminatorValue = derivedType.GetDiscriminatorValue();\n                if (!derivedType.ClrType.IsInstantiable()\n                    || discriminatorValue is null)\n                {\n                    continue;\n                }\n\n                if (discriminatorValue is not string valueString)\n                {\n                    throw new InvalidOperationException(\n                        RelationalStrings.NonTphDiscriminatorValueNotString(discriminatorValue, derivedType.DisplayName()));\n                }\n\n                if (discriminatorValues.TryGetValue(valueString, out var duplicateEntityType))\n                {\n                    throw new InvalidOperationException(\n                        RelationalStrings.EntityShortNameNotUnique(\n                            derivedType.Name, discriminatorValue, duplicateEntityType.Name));\n                }\n\n                discriminatorValues[valueString] = derivedType;\n            }\n        }\n    }\n\n    /// <summary>\n    ///     Validates the key value generation is valid.\n    /// </summary>","sourceCodeStart":2133,"sourceCodeEnd":2169,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs#L2133-L2169","documentation":"In a non-TPH hierarchy (TPT or TPC), EF Core uses the entity 'short name' as an implicit discriminator value, and these implicit discriminator values must be strings. A configured discriminator value that is not a string was found on a derived type. Thrown from ValidateInheritanceMapping while iterating derived types in a non-TPH hierarchy.","triggerScenarios":"Calling HasDiscriminator(...).HasValue(42) (int) or .HasValue(MyEnum.A) on a derived entity in a hierarchy configured with UseTptMappingStrategy() or UseTpcMappingStrategy(); using an enum or numeric discriminator value in TPT/TPC.","commonSituations":"Carrying over an int/enum discriminator configuration when migrating from TPH to TPT/TPC; using value-typed discriminators by habit in a TPC hierarchy; mixing TPH-style discriminator values into a non-TPH hierarchy.","solutions":["Change HasValue(...) to supply a string discriminator value (e.g. HasValue(\"Student\")).","Remove the custom discriminator value and let EF use the entity short name (a string by default).","If a non-string discriminator is required, use TPH instead of TPT/TPC."],"exampleFix":"// before (TPT hierarchy)\nmodelBuilder.Entity<Person>()\n    .UseTptMappingStrategy()\n    .HasDiscriminator(p => p.Kind)\n    .HasValue<Student>(1)\n    .HasValue<Teacher>(2);\n// after (string discriminator values in TPT/TPC)\nmodelBuilder.Entity<Person>()\n    .UseTptMappingStrategy()\n    .HasDiscriminator(p => p.Kind)\n    .HasValue<Student>(\"Student\")\n    .HasValue<Teacher>(\"Teacher\");","handlingStrategy":"validation","validationCode":"using (var ctx = new MyContext()) { ctx.Model.ToDebugString(); }\n// Assert all discriminator values in non-TPH hierarchies are strings.\nforeach (var root in ctx.Model.GetEntityTypes().Where(e => e.BaseType == null))\n{\n    var s = (string?)root[Microsoft.EntityFrameworkCore.Metadata.RelationalAnnotationNames.MappingStrategy];\n    if (s is null or \"TPH\") continue;\n    foreach (var d in root.GetDerivedTypesInclusive())\n    {\n        var v = d.GetDiscriminatorValue();\n        if (v is not null and not string)\n            Debug.Fail($\"Discriminator value {v} ({v!.GetType().Name}) on {d.Name} is not a string under {s}.\");\n    }\n}","typeGuard":null,"tryCatchPattern":"try { using var ctx = new MyContext(); _ = ctx.Model; }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"is not a string. Configure a string discriminator value\"))\n{ log.Error(\"Non-string discriminator in TPT/TPC; use a string value or switch to TPH: {Msg}\", ex.Message); throw; }","preventionTips":["In TPT/TPC hierarchies, pass string discriminator values to HasValue(...) or omit them to use entity short names.","When migrating from TPH, sweep and convert any int/enum discriminator values.","Cover discriminator-value types in a startup model-build test."],"tags":["ef-core","model-validation","discriminator","inheritance","mapping-strategy","tpt","tpc"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}