dotnet/efcore · error · InvalidOperationException
SelectExpressionNonTphWithCustomTable
SelectExpressionNonTphWithCustomTable
Error message
Cannot create a 'SelectExpression' with a custom 'TableExpressionBase' since the result type {entityType} is part of a hierarchy and does not contain a discriminator property. What it means
Thrown by CreateSelect when a SelectExpression is built over a custom TableExpressionBase (FromSqlExpression, TableValuedFunctionExpression, etc.) for an entity type that participates in an inheritance hierarchy but has no discriminator property. EF needs a discriminator to know which concrete type each row represents when composing over a custom (non-mapped) table source; without one it cannot build a valid query.
Source
Thrown at src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs:665
complexProperty.ClrType,
complexProperty.IsCollection);
return complexProperty.IsCollection
? new CollectionResultExpression(jsonQuery, complexProperty, elementType: complexType.ClrType)
: new RelationalStructuralTypeShaperExpression(complexType, jsonQuery, isNullable);
}
/// <summary>
/// Used to create a <see cref="SelectExpression" /> representing a query of the given entity type, when its table expression has
/// already been constructed externally. This overload is used for cases such as <see cref="FromSqlExpression" />,
/// <see cref="TableValuedFunctionExpression" />, etc.
/// </summary>
private SelectExpression CreateSelect(IEntityType entityType, TableExpressionBase tableExpressionBase)
{
if ((entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any())
&& entityType.FindDiscriminatorProperty() == null)
{
throw new InvalidOperationException(RelationalStrings.SelectExpressionNonTphWithCustomTable(entityType.DisplayName()));
}
if (tableExpressionBase is not ITableBasedExpression { Table: ITableBase table })
{
throw new UnreachableException("SelectExpression with unexpected missing table");
}
var select = GenerateSingleTableSelect(entityType, table, tableExpressionBase);
AddEntitySelectConditions(select, entityType);
return select;
}
/***
* We need to add additional conditions on basic SelectExpression for certain cases
* - If we are selecting from TPH then we need to add condition for discriminator if mapping is incomplete
* - When we are selecting optional dependent sharing table, we need to add condition to figure out existence
* ** Optional Dependent **View on GitHub (pinned to dbf9771522)
Solutions
- Restrict FromSql/TVF usage to leaf entity types in non-TPH hierarchies, or to types that have a discriminator.
- Switch the hierarchy back to TPH (which provides the discriminator) if FromSql over the base type is required.
- Project to a non-entity DTO from FromSql instead of binding directly to the hierarchical entity type.
- Provide a discriminator column in the custom SQL result and configure it on the entity type.
Example fix
// before - base type in a TPT hierarchy, no discriminator
var animals = db.Animals.FromSqlRaw("SELECT * FROM v_Animals").ToList();
// after - query a concrete leaf type instead
var dogs = db.Dogs.FromSqlRaw("SELECT * FROM v_Animals").ToList();
// or switch the hierarchy to TPH so a discriminator exists Defensive patterns
Strategy: validation
Validate before calling
static bool CanFromSqlOver(IEntityType et)
=> et.FindDiscriminatorProperty() is not null
|| (et.BaseType is null && !et.GetDirectlyDerivedTypes().Any());
var entityType = db.Model.FindEntityType(typeof(Animal))!;
if (!CanFromSqlOver(entityType))
throw new InvalidOperationException("FromSql over a hierarchical type requires a discriminator; query a leaf type instead."); Try / catch
try { return db.Animals.FromSqlRaw(sql).ToList(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("does not contain a discriminator property"))
{
// query a concrete leaf type or switch to TPH
return db.Set<Dog>().FromSqlRaw(sql).ToList();
} Prevention
- Use FromSql/TVFs only on leaf entity types or types that have a discriminator.
- Keep TPH (which provides a discriminator) when FromSql over base types is required.
- Project FromSql results to DTOs rather than binding to hierarchical entities.
- Document which entity types are safe to query via FromSql.
When it happens
Trigger: Calling FromSqlRaw/FromSqlInterpolated (or a mapped TVF) on an entity that is a base/derived type in a TPT or TPC hierarchy (no discriminator), or any hierarchy where a discriminator property is not configured. Composing LINQ over that FromSql/TVF query then triggers CreateSelect.
Common situations: Switching an inheritance hierarchy from TPH to TPT/TPC and still using FromSqlRaw on the base type; mapping a TVF to an abstract base entity; removing a discriminator configuration while keeping FromSql calls.
Related errors
- Using '{methodName}' on DbSet of '{entityType}' is not suppo
- The mapping strategy '{mappingStrategy}' specified on '{enti
- The specified discriminator value '{value}' for '{entityType
- The short name for '{entityType1}' is '{discriminatorValue}'
- The derived entity type '{entityType}' was configured with t
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/7f1eba45550d6d0c.
Report an issue: GitHub.