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

  1. Restrict FromSql/TVF usage to leaf entity types in non-TPH hierarchies, or to types that have a discriminator.
  2. Switch the hierarchy back to TPH (which provides the discriminator) if FromSql over the base type is required.
  3. Project to a non-entity DTO from FromSql instead of binding directly to the hierarchical entity type.
  4. 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

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


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/7f1eba45550d6d0c. Report an issue: GitHub.