{"record":{"id":"7f1eba45550d6d0c","repo":"dotnet/efcore","slug":"selectexpressionnontphwithcustomtable","errorCode":"SelectExpressionNonTphWithCustomTable","errorMessage":"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.","messagePattern":"Cannot create a 'SelectExpression' with a custom 'TableExpressionBase' since the result type (.+?) is part of a hierarchy and does not contain a discriminator property\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs","lineNumber":665,"sourceCode":"            complexProperty.ClrType,\n            complexProperty.IsCollection);\n\n        return complexProperty.IsCollection\n            ? new CollectionResultExpression(jsonQuery, complexProperty, elementType: complexType.ClrType)\n            : new RelationalStructuralTypeShaperExpression(complexType, jsonQuery, isNullable);\n    }\n\n    /// <summary>\n    ///     Used to create a <see cref=\"SelectExpression\" /> representing a query of the given entity type, when its table expression has\n    ///     already been constructed externally. This overload is used for cases such as <see cref=\"FromSqlExpression\" />,\n    ///     <see cref=\"TableValuedFunctionExpression\" />, etc.\n    /// </summary>\n    private SelectExpression CreateSelect(IEntityType entityType, TableExpressionBase tableExpressionBase)\n    {\n        if ((entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any())\n            && entityType.FindDiscriminatorProperty() == null)\n        {\n            throw new InvalidOperationException(RelationalStrings.SelectExpressionNonTphWithCustomTable(entityType.DisplayName()));\n        }\n\n        if (tableExpressionBase is not ITableBasedExpression { Table: ITableBase table })\n        {\n            throw new UnreachableException(\"SelectExpression with unexpected missing table\");\n        }\n\n        var select = GenerateSingleTableSelect(entityType, table, tableExpressionBase);\n        AddEntitySelectConditions(select, entityType);\n\n        return select;\n    }\n\n    /***\n     * We need to add additional conditions on basic SelectExpression for certain cases\n     * - If we are selecting from TPH then we need to add condition for discriminator if mapping is incomplete\n     * - When we are selecting optional dependent sharing table, we need to add condition to figure out existence\n     *  ** Optional Dependent **","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs#L647-L683","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before - base type in a TPT hierarchy, no discriminator\nvar animals = db.Animals.FromSqlRaw(\"SELECT * FROM v_Animals\").ToList();\n// after - query a concrete leaf type instead\nvar dogs = db.Dogs.FromSqlRaw(\"SELECT * FROM v_Animals\").ToList();\n// or switch the hierarchy to TPH so a discriminator exists","handlingStrategy":"validation","validationCode":"static bool CanFromSqlOver(IEntityType et)\n    => et.FindDiscriminatorProperty() is not null\n       || (et.BaseType is null && !et.GetDirectlyDerivedTypes().Any());\n\nvar entityType = db.Model.FindEntityType(typeof(Animal))!;\nif (!CanFromSqlOver(entityType))\n    throw new InvalidOperationException(\"FromSql over a hierarchical type requires a discriminator; query a leaf type instead.\");","typeGuard":null,"tryCatchPattern":"try { return db.Animals.FromSqlRaw(sql).ToList(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"does not contain a discriminator property\"))\n{\n    // query a concrete leaf type or switch to TPH\n    return db.Set<Dog>().FromSqlRaw(sql).ToList();\n}","preventionTips":["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."],"tags":["fromsql","inheritance","tpt","tpc","discriminator","query-translation"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}