dotnet/efcore · error · InvalidOperationException
Using ' ' on DbSet of ' ' is not supported since ' ' is…
Error message
Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. What it means
GenerateFromSqlQueryRoot throws MethodOnNonTphRootNotSupported when FromSqlRaw/FromSqlInterpolated is called on a DbSet whose entity type participates in an inheritance hierarchy (has a base type or derived types) but has no discriminator property. FromSql can only target the TPH root that owns the discriminator so EF can filter rows; an inheritance branch without a discriminator cannot be the SQL root.
Solutions
- Call FromSqlRaw on the hierarchy root DbSet and add a WHERE clause filtering by discriminator/column values.
- If TPH, configure HasDiscriminator on the root entity so the check passes.
- For TPT/TPC, query the root and project, or use a plain SqlQueryRaw<T> for the leaf type instead of DbSet.FromSql.
Example fix
// before (DerivedEntity is in a hierarchy, no discriminator)
var rows = await db.Set<DerivedEntity>()
.FromSqlRaw("SELECT * FROM derived_view").ToListAsync();
// after — query the root and filter
var rows = await db.Set<BaseEntity>()
.FromSqlRaw("SELECT * FROM base_view WHERE Discriminator = 'DerivedEntity'")
.OfType<DerivedEntity>().ToListAsync(); Defensive patterns
Strategy: validation
Validate before calling
var entityType = db.Model.FindEntityType(typeof(TEntity))!;
if ((entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any())
&& entityType.FindDiscriminatorProperty() == null)
throw new InvalidOperationException("Cannot FromSql on this hierarchy node; query the root."); Type guard
static bool CanFromSql(IEntityType e)
=> e.BaseType == null && !e.GetDirectlyDerivedTypes().Any()
|| e.FindDiscriminatorProperty() is not null; Try / catch
try { return await db.Set<T>().FromSqlRaw(sql).ToListAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("does not contain a discriminator"))
{
// fall back to querying the root and filtering
return await db.Set<TRoot>().FromSqlRaw(sql).OfType<T>().ToListAsync();
} Prevention
- Always call FromSql on the hierarchy root, not on derived sets.
- Configure HasDiscriminator when using TPH with raw SQL.
- Prefer SqlQueryRaw<T> for leaf-type ad-hoc queries that bypass inheritance.
When it happens
Trigger: Calling context.Set<DerivedEntity>().FromSqlRaw(...) where DerivedEntity is part of TPT/TPC inheritance, or TPH without a configured discriminator. The check is `entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any()` combined with `FindDiscriminatorProperty() == null`.
Common situations: Switching from TPH to TPT/TPC and reusing existing FromSql calls; scaffolding a hierarchy without a discriminator column; forgetting HasDiscriminator on an abstract base; calling FromSql on a leaf entity in a hierarchy.
Related errors
- The element type of the result of
- The mapping strategy
- Cannot create a 'SelectExpression' with a custom…
- The derived entity type
- The mapping strategy
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/c6c9a303812d6911.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs:192
return queryableSource.Provider.CreateQuery<TEntity>(
GenerateFromSqlQueryRoot(
queryableSource,
sql.Format,
sql.GetArguments()));
}
private static FromSqlQueryRootExpression GenerateFromSqlQueryRoot(
IQueryable source,
string sql,
object?[] arguments,
[CallerMemberName] string memberName = null!)
{
var entityQueryRootExpression = (EntityQueryRootExpression)source.Expression;
var entityType = entityQueryRootExpression.EntityType;
return (entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any())
&& entityType.FindDiscriminatorProperty() == null
? throw new InvalidOperationException(
RelationalStrings.MethodOnNonTphRootNotSupported(memberName, entityType.DisplayName()))
: new FromSqlQueryRootExpression(
entityQueryRootExpression.QueryProvider!,
entityType,
sql,
Expression.Constant(arguments));
}
#endregion
#region SplitQuery
/// <summary>
/// Returns a new query which is configured to load the collections in the query results in a single database query.
/// </summary>
/// <remarks>
/// <para>
/// This behavior generally guarantees result consistency in the face of concurrent updatesView on GitHub (pinned to 3a2006ef56)