Fix TPC returning incorrect entity types for generic derived types#38022
Fix TPC returning incorrect entity types for generic derived types#38022
Conversation
Agent-Logs-Url: https://github.com/dotnet/efcore/sessions/c0024813-2a50-456b-a3fb-42fe4a8ce43b Co-authored-by: roji <1862641+roji@users.noreply.github.com>
roji
left a comment
There was a problem hiding this comment.
The proposed fix looks like it would fail immediately if the discriminator value isn't a string (e.g. enum).
Please check what the update pipeline is doing when saving entities in a TPC hierarchy, i.e. how it's managing the discriminator; replicate that logic.
The
Regarding the update pipeline: TPC tables have no discriminator columns, so the update pipeline doesn't use discriminator values at all — it writes to the correct table via the entity type's table mapping. The discriminator in the TPC The root cause was Commit: 59e5fc0 |
|
@AndriySvyryd can I have your eyes on this? The above makes sense to me - the problem seems to be only with non-persisted (non-TPH) discriminators, which we seem to always handle as strings; but I just want to be extra sure. |
There was a problem hiding this comment.
Pull request overview
Fixes incorrect entity materialization for TPC hierarchies with generic concrete derived types by ensuring discriminator constants are generated from the configured/default discriminator value (which preserves generic arguments) rather than from ShortName() (which strips them), preventing discriminator collisions in UNION ALL queries.
Changes:
- Use
GetDiscriminatorValue()(cast tostring) when generating discriminator constants for TPC UNION ALL branches and TPT CASE expressions. - Use
GetDiscriminatorValue()when mapping discriminator values back toIEntityTypeduring materialization and when generating type predicates. - Add a relational spec test validating correct materialization and
OfType<T>behavior for TPC with generic derived types.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs | Adds coverage for TPC materialization and OfType<T> with generic derived concrete types. |
| src/EFCore.Relational/Query/RelationalStructuralTypeShaperExpression.cs | Updates discriminator-to-entity-type switch cases to use GetDiscriminatorValue() for TPT/TPC shaping. |
| src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs | Updates TPT/TPC type predicate generation to use GetDiscriminatorValue() (preserving generics). |
| src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs | Updates single-concrete-type TPC discriminator injection to use GetDiscriminatorValue(). |
| src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.CreateSelect.cs | Updates TPT CASE discriminator results and TPC UNION discriminator projections to use GetDiscriminatorValue(). |
ShortName()strips generic type parameters, so bothReproEntity<int>andReproEntity<string>produced the same TPC discriminator'ReproEntity'in UNION ALL queries — causing all rows to materialize as the first concrete type.Changes
CreateSelect.cs— ReplaceShortName()with(string)GetDiscriminatorValue()!when generating discriminator constants for TPC UNION ALL branches and TPT CASE WHEN expressionsRelationalStructuralTypeShaperExpression.cs— Same fix for the materializer switch cases that map discriminator string values back toIEntityTypeShaperProcessingExpressionVisitor.cs— Same fix for the single-concrete-type TPC optimization that injects the discriminator value as a constant (no column in SQL)RelationalSqlTranslatingExpressionVisitor.cs— Same fix for type predicate generation (is/==expressions) in TPT/TPC hierarchiesGetDiscriminatorValue()falls back toClrType.ShortDisplayName()(which preserves generics, e.g."ReproEntity<int>") when no explicit discriminator value is configured — so existing non-generic models are unaffected.Repro:
where the model uses
UseTpcMappingStrategy()withReproEntity<int>andReproEntity<string>as concrete types.⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.