dotnet/efcore · error · InvalidOperationException
The short name for '{entityType1}' is '{discriminatorValue}'
Error message
The short name for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique short name. Either rename one of the types or call 'modelBuilder.Entity<TEntity>().Metadata.SetDiscriminatorValue("NewShortName")'. What it means
Thrown during model validation for a non-TPH hierarchy (TPT or TPC). In non-TPH mappings EF uses each concrete entity type's discriminator value (which defaults to the entity's short/display name) as a unique key when materializing instances across the hierarchy. When two instantiable derived types resolve to the same string discriminator value, EF cannot tell them apart and refuses to build the model. The check lives in ValidateDiscriminatorValues inside the non-TPH branch of ValidateInheritanceMapping.
Source
Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:2157
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>
/// <param name="key">The key.</param>
/// <param name="logger">The logger to use.</param>
protected virtual void ValidateValueGeneration(
IKey key,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{View on GitHub (pinned to dbf9771522)
Solutions
- Explicitly assign a unique discriminator value on one of the colliding types via modelBuilder.Entity<DerivedA>().Metadata.SetDiscriminatorValue("DerivedA_Orders") or .HasDiscriminator().HasValue<DerivedA>("DerivedA_Orders").
- Rename one of the colliding CLR classes (or its entity display name) so the default short name no longer collides.
- If you intended TPH instead of TPT/TPC, ensure a discriminator property is configured on the root so the model takes the TPH validation path instead.
- Inspect the two type names reported in the message to confirm which pair collides, then disambiguate only those two.
Example fix
// before
modelBuilder.Entity<Orders.Derived>().HasBaseType<Base>();
modelBuilder.Entity<Billing.Derived>().HasBaseType<Base>(); // both default discriminator == "Derived"
// after
modelBuilder.Entity<Orders.Derived>().HasBaseType<Base>()
.HasDiscriminator().HasValue<Orders.Derived>("Orders_Derived");
modelBuilder.Entity<Billing.Derived>().HasBaseType<Base>()
.HasDiscriminator().HasValue<Billing.Derived>("Billing_Derived"); Defensive patterns
Strategy: validation
Validate before calling
// After configuring the model, verify discriminator short-name uniqueness before first use.
using Microsoft.EntityFrameworkCore;
bool HasUniqueDiscriminatorShortNames(DbContext context)
{
foreach (var root in context.Model.GetEntityTypes()
.Where(e => e.BaseType == null && e.GetDerivedTypes().Any()
&& e.FindDiscriminatorProperty() == null
&& e.GetMappingStrategy() is "TPT" or "TPC"))
{
var seen = new HashSet<string>();
foreach (var d in root.GetDerivedTypesInclusive())
{
if (!d.ClrType.IsInstantiable()) continue;
var v = d.GetDiscriminatorValue();
if (v is not string s) continue;
if (!seen.Add(s)) return false; // collision
}
}
return true;
} Try / catch
// Wrap the first model-building operation to surface a friendly message.
try
{
_ = context.Model; // triggers validation
}
catch (InvalidOperationException ex) when (ex.Message.Contains("short name", StringComparison.Ordinal))
{
throw new InvalidOperationException("Duplicate discriminator short name in a TPT/TPC hierarchy. Set a unique value via HasDiscriminator().HasValue<T>(\"name\").", ex);
} Prevention
- Default to HasDiscriminator().HasValue<T>(value) for every derived type in TPT/TPC rather than relying on the type name.
- Run a model smoke test (access context.Model) in your test suite so collisions surface at test time, not production.
- Keep entity class names unique within a hierarchy even across namespaces.
When it happens
Trigger: Triggered when, for a root entity whose discriminator property is null but mapping strategy is TPT or TPC, two instantiable derived types share the same string discriminator value. Commonly happens because the discriminator value defaults to the CLR type's simple name and you have two entity types in different namespaces with the same class name, or you explicitly called HasDiscriminator().SetValueConverter / SetDiscriminatorValue with a colliding value.
Common situations: Renaming/refactoring an entity to share a name with a sibling; integrating two modules whose derived types are both called e.g. 'Order' but live in different namespaces; bulk-generating entity classes from a schema where type-name collisions slip through; copying a derived entity class and forgetting to change its discriminator.
Related errors
- The mapping strategy '{mappingStrategy}' specified on '{enti
- The specified discriminator value '{value}' for '{entityType
- Both '{entityType}' and '{otherEntityType}' are mapped to th
- Both '{entityType}' and '{otherEntityType}' are mapped to th
- Both '{entityType}' and '{otherEntityType}' are mapped to th
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/a625818103e96275.
Report an issue: GitHub.