{"record":{"id":"f258c440a713b4dc","repo":"dotnet/efcore","slug":"both-entitytype-and-otherentitytype-are-ma","errorCode":null,"errorMessage":"Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.","messagePattern":"Both '(.+?)' and '(.+?)' are mapped to the table '(.+?)'\\. All the entity types in a non-TPH hierarchy \\(one that doesn't have a discriminator\\) must be mapped to different tables\\. See https://go\\.microsoft\\.com/fwlink/\\?linkid=2130430 for more information\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs","lineNumber":2241,"sourceCode":"                    && entityType.GetDerivedTypes().Any(derived => StoreObjectIdentifier.Create(derived, storeObjectType) != null))\n                {\n                    throw new InvalidOperationException(\n                        RelationalStrings.UnmappedNonTPHOwner(\n                            entityType.DisplayName(),\n                            unmappedOwnedType.FindOwnership()!.PrincipalToDependent?.Name,\n                            unmappedOwnedType.DisplayName(),\n                            storeObjectType));\n                }\n\n                continue;\n            }\n\n            if (derivedTypes.TryGetValue(storeObject.Value, out var otherType))\n            {\n                switch (storeObjectType)\n                {\n                    case StoreObjectType.Table:\n                        throw new InvalidOperationException(\n                            RelationalStrings.NonTphTableClash(\n                                entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));\n                    case StoreObjectType.View:\n                        throw new InvalidOperationException(\n                            RelationalStrings.NonTphViewClash(\n                                entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));\n                    case StoreObjectType.InsertStoredProcedure:\n                    case StoreObjectType.DeleteStoredProcedure:\n                    case StoreObjectType.UpdateStoredProcedure:\n                        throw new InvalidOperationException(\n                            RelationalStrings.NonTphStoredProcedureClash(\n                                entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));\n                }\n            }\n\n            if (isTpc)\n            {\n                var rowInternalFk = entityType.FindDeclaredReferencingRowInternalForeignKeys(storeObject.Value)","sourceCodeStart":2223,"sourceCodeEnd":2259,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs#L2223-L2259","documentation":"ValidateNonTphMapping tracks each store object that an entity type in a non-TPH hierarchy maps to. Because a non-TPH hierarchy has no discriminator to disambiguate rows, every type must land in its own table; when two types in the same hierarchy resolve to the same Table StoreObjectIdentifier, EF throws to prevent ambiguous inserts/queries.","triggerScenarios":"Produced when two entity types in a discriminator-less hierarchy both call ToTable(\"X\") (or inherit the same table name) and the validator detects storeObjectType == StoreObjectType.Table collision in derivedTypes dictionary. Common when explicit ToTable names are copy-pasted across TPT siblings.","commonSituations":"TPT mapping where sibling derived types accidentally share a ToTable name; scaffolded mappings that reused the base table for a derived type; renaming a table and forgetting to update one sibling.","solutions":["Give each derived type in the TPT/TPC hierarchy a distinct ToTable name.","If you actually want all types in one table, switch the root to TPH (configure a discriminator property) so the validator takes the TPH path.","Remove the redundant ToTable call on the derived type so it falls back to its own default name."],"exampleFix":"// before\nmodelBuilder.Entity<Manager>().ToTable(\"People\");\nmodelBuilder.Entity<Employee>().ToTable(\"People\"); // TPT without discriminator\n\n// after\nmodelBuilder.Entity<Manager>().ToTable(\"Managers\");\nmodelBuilder.Entity<Employee>().ToTable(\"Employees\");","handlingStrategy":"validation","validationCode":"bool NoNonTphTableClash(DbContext context)\n{\n    foreach (var root in context.Model.GetEntityTypes()\n        .Where(e => e.BaseType == null && e.FindDiscriminatorProperty() == null && e.GetDerivedTypes().Any()))\n    {\n        var names = new HashSet<StoreObjectIdentifier>();\n        foreach (var et in root.GetDerivedTypesInclusive())\n        {\n            var so = StoreObjectIdentifier.Create(et, StoreObjectType.Table);\n            if (so.HasValue && !names.Add(so.Value)) return false;\n        }\n    }\n    return true;\n}","typeGuard":null,"tryCatchPattern":"try { _ = context.Model; }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"non-TPH hierarchy\", StringComparison.Ordinal) && ex.Message.Contains(\"table '\", StringComparison.Ordinal))\n{\n    throw new InvalidOperationException(\"Two types in a TPT/TPC hierarchy share a table. Give each a distinct ToTable, or switch to TPH.\", ex);\n}","preventionTips":["In TPT/TPC, always set a distinct ToTable per concrete type.","If one shared table is the goal, configure a discriminator and use TPH instead.","Write a test that asserts each hierarchy member maps to a unique table."],"tags":["ef-core","model-validation","inheritance","tpt","tpc","tables","relational"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}