dotnet/efcore · error · InvalidOperationException
The specified discriminator value
Error message
The specified discriminator value '{value}' for '{entityType}' is not a string. Configure a string discriminator value instead. What it means
Thrown inside ValidateDiscriminatorValues (the non-TPH branch) when a concrete derived type's discriminator value, returned by GetDiscriminatorValue(), cannot be cast to string. In TPT/TPC hierarchies the discriminator value defaults to the entity's short name (a string), so any explicitly-set non-string discriminator value is rejected.
Solutions
- Set a string discriminator value instead: SetDiscriminatorValue("Customer").
- Remove the custom discriminator value and let EF use the entity short name automatically.
- If you need numeric discriminator semantics, stay on TPH mapping where non-string discriminator values are supported.
Example fix
// before (TPT hierarchy)
modelBuilder.Entity<Customer>().HasBaseType<Person>()
.Property("Discriminator").HasConversion<string>();
modelBuilder.Entity<Customer>().Metadata.SetDiscriminatorValue(2); // int -> throws 422
// after
modelBuilder.Entity<Customer>().Metadata.SetDiscriminatorValue("Customer"); Defensive patterns
Strategy: validation
Validate before calling
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(t => t.BaseType != null))
{
var root = entityType.GetRootType();
var strategy = root.GetMappingStrategy();
if (strategy != RelationalAnnotationNames.TphMappingStrategy
&& strategy != null)
{
var dv = entityType.GetDiscriminatorValue();
if (entityType.ClrType.IsInstantiable() && dv is not null and not string)
{
throw new InvalidOperationException($"{entityType.DisplayName()} has non-string discriminator value {dv}; set a string.");
}
}
} Prevention
- In TPT/TPC hierarchies, only ever set string discriminator values (or none at all).
- Keep integer/enum discriminator values for TPH-only models.
- Add a test that finalizes the model so this surfaces during CI rather than at runtime.
When it happens
Trigger: In a TPT or TPC hierarchy, calling SetDiscriminatorValue(42) or SetDiscriminatorValue(true) (any non-string), or applying a [Discriminator] with an int/enum/bool value on a derived type. Only abstract/non-instantiable types are skipped; concrete types are checked.
Common situations: Porting a TPH model that used integer discriminator values into a TPT/TPC hierarchy without changing them; assuming discriminator values are type-flexible across all strategies.
Related errors
- The mapping strategy
- The short name for ' ' is ' ' which is the same for ' '…
- Both ' ' and ' ' are mapped to the table ' '. All the…
- Both ' ' and ' ' are mapped to the stored procedure ' '…
- Both ' ' and ' ' are mapped to the view ' '. All the entity…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/bfa9571fef3e52ba.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:2151
var derivedTypes = entityType.GetDerivedTypesInclusive().ToList();
var discriminatorValues = new Dictionary<string, IEntityType>();
foreach (var derivedType in derivedTypes)
{
foreach (var complexProperty in derivedType.GetDeclaredComplexProperties())
{
ValidateDiscriminatorValues(complexProperty.ComplexType);
}
var discriminatorValue = derivedType.GetDiscriminatorValue();
if (!derivedType.ClrType.IsInstantiable()
|| discriminatorValue is null)
{
continue;
}
if (discriminatorValue is not string valueString)
{
throw new InvalidOperationException(
RelationalStrings.NonTphDiscriminatorValueNotString(discriminatorValue, derivedType.DisplayName()));
}
if (discriminatorValues.TryGetValue(valueString, out var duplicateEntityType))
{
throw new InvalidOperationException(
RelationalStrings.EntityShortNameNotUnique(
derivedType.Name, discriminatorValue, duplicateEntityType.Name));
}
discriminatorValues[valueString] = derivedType;
}
}
}
/// <summary>
/// Validates the key value generation is valid.
/// </summary>View on GitHub (pinned to 3a2006ef56)