diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 1dd58982534..5b404c0563e 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -601,6 +601,7 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property, RelationalAnnotationNames.ColumnType, RelationalAnnotationNames.TableColumnMappings, RelationalAnnotationNames.ViewColumnMappings, + RelationalAnnotationNames.RelationalOverrides, CoreAnnotationNames.ValueGeneratorFactory, CoreAnnotationNames.PropertyAccessMode, CoreAnnotationNames.ChangeTrackingStrategy, @@ -849,7 +850,9 @@ protected virtual void GenerateEntityTypeAnnotations( var discriminatorMappingCompleteAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorMappingComplete); var discriminatorValueAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorValue); - if ((discriminatorPropertyAnnotation ?? discriminatorMappingCompleteAnnotation ?? discriminatorValueAnnotation) != null) + if ((discriminatorPropertyAnnotation?.Value + ?? discriminatorMappingCompleteAnnotation?.Value + ?? discriminatorValueAnnotation?.Value) != null) { stringBuilder .AppendLine() diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index d6c6662b0e2..cc9685b26fd 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -251,7 +251,8 @@ private IEnumerable GetAnnotationNamespaces(IEnumerable it RelationalAnnotationNames.ViewColumnMappings, RelationalAnnotationNames.ForeignKeyMappings, RelationalAnnotationNames.TableIndexMappings, - RelationalAnnotationNames.UniqueConstraintMappings + RelationalAnnotationNames.UniqueConstraintMappings, + RelationalAnnotationNames.RelationalOverrides }; return items.SelectMany( diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 941684b3aef..c9e186a2f11 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -663,6 +663,7 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) RemoveAnnotation(ref annotations, RelationalAnnotationNames.IsFixedLength); RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableColumnMappings); RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewColumnMappings); + RemoveAnnotation(ref annotations, RelationalAnnotationNames.RelationalOverrides); RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.ColumnOrdinal); if (!useDataAnnotations) diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnListComparer.cs b/src/EFCore.Relational/Metadata/Internal/ColumnListComparer.cs index f10c8d92adb..5817aa1542d 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnListComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnListComparer.cs @@ -36,7 +36,6 @@ private ColumnListComparer() public int Compare(IReadOnlyList x, IReadOnlyList y) { var result = x.Count - y.Count; - if (result != 0) { return result; diff --git a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs index 1d24e05ca7c..37e5cc2505f 100644 --- a/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/ColumnMappingBaseComparer.cs @@ -34,9 +34,7 @@ private ColumnMappingBaseComparer() /// public int Compare(IColumnMappingBase x, IColumnMappingBase y) { - var result = x.Column.IsNullable == y.Column.IsNullable - ? 0 - : x.Column.IsNullable ? 1 : -1; + var result = y.Column.IsNullable.CompareTo(x.Column.IsNullable); if (result != 0) { return result; diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 393235d8703..8211b7e127d 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -81,6 +81,7 @@ public static IModel Add( var schema = entityType.GetSchema(); TableMapping lastMapping = null; var mappedType = entityType; + SortedSet tableMappings = null; while (mappedType != null) { var mappedTable = mappedType.GetTableName(); @@ -147,7 +148,7 @@ public static IModel Add( continue; } - var tableMappings = entityType[RelationalAnnotationNames.TableMappings] as SortedSet; + tableMappings = entityType[RelationalAnnotationNames.TableMappings] as SortedSet; if (tableMappings == null) { tableMappings = new SortedSet(TableMappingBaseComparer.EntityTypeInstance); @@ -160,7 +161,10 @@ public static IModel Add( lastMapping = tableMapping; } + // Re-add the mapping to update the order + tableMappings.Remove(lastMapping); lastMapping.IsMainTableMapping = true; + tableMappings.Add(lastMapping); } var viewName = entityType.GetViewName(); @@ -168,6 +172,7 @@ public static IModel Add( { var schema = entityType.GetViewSchema(); ViewMapping lastMapping = null; + SortedSet viewMappings = null; var mappedType = entityType; while (mappedType != null) { @@ -232,7 +237,7 @@ public static IModel Add( continue; } - var viewMappings = entityType[RelationalAnnotationNames.ViewMappings] as SortedSet; + viewMappings = entityType[RelationalAnnotationNames.ViewMappings] as SortedSet; if (viewMappings == null) { viewMappings = new SortedSet(TableMappingBaseComparer.EntityTypeInstance); @@ -245,7 +250,10 @@ public static IModel Add( lastMapping = viewMapping; } + // Re-add the mapping to update the order + viewMappings.Remove(lastMapping); lastMapping.IsMainTableMapping = true; + viewMappings.Add(lastMapping); } } @@ -600,7 +608,19 @@ private static void PopulateRowInternalForeignKeys(TableBase table) } } - mainMapping.IsMainEntityTypeMapping = true; + // Re-add the mapping to update the order + if (mainMapping is TableMapping mainTableMapping) + { + ((Table)mainMapping.Table).EntityTypeMappings.Remove(mainTableMapping); + mainMapping.IsMainEntityTypeMapping = true; + ((Table)mainMapping.Table).EntityTypeMappings.Add(mainTableMapping); + } + else + { + ((View)mainMapping.Table).EntityTypeMappings.Remove((ViewMapping)mainMapping); + mainMapping.IsMainEntityTypeMapping = true; + ((View)mainMapping.Table).EntityTypeMappings.Add((ViewMapping)mainMapping); + } if (referencingInternalForeignKeyMap != null) { diff --git a/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs index ad8b1bc35ca..873ed6bc7d8 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableMappingBaseComparer.cs @@ -48,8 +48,8 @@ private TableMappingBaseComparer(bool forEntityType) public int Compare(ITableMappingBase x, ITableMappingBase y) { var result = _isForEntityType - ? x.IsMainTableMapping.CompareTo(y.IsMainTableMapping) - : x.IsMainEntityTypeMapping.CompareTo(y.IsMainEntityTypeMapping); + ? y.IsMainTableMapping.CompareTo(x.IsMainTableMapping) + : y.IsMainEntityTypeMapping.CompareTo(x.IsMainEntityTypeMapping); if (result != 0) { return result; diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 47a4bb08254..c031177f987 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using JetBrains.Annotations; @@ -68,10 +69,8 @@ public class MigrationsModelDiffer : IMigrationsModelDiffer private IUpdateAdapter _sourceUpdateAdapter; private IUpdateAdapter _targetUpdateAdapter; - private readonly Dictionary> _sourceSharedTableEntryMaps = - new Dictionary>(); - private readonly Dictionary>> _targetSharedTableEntryMaps = - new Dictionary>>(); + private readonly Dictionary _sourceSharedTableEntryMaps = + new Dictionary(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1785,25 +1784,12 @@ protected virtual Dictionary> DiffData( if (targetTableMapping.IsMainTableMapping) { mainSourceTable = diffContext.FindSource(targetTable); - if (!_targetSharedTableEntryMaps.TryGetValue(targetTable, out var targetTableEntryMappingMap)) - { - targetTableEntryMappingMap = SharedTableEntryMap>.CreateSharedTableEntryMapFactory( - targetTable, - _targetUpdateAdapter) - ((_, __, ___) => new List()); - _targetSharedTableEntryMaps.Add(targetTable, targetTableEntryMappingMap); - } - - foreach (var targetSeed in targetEntityType.GetSeedData()) - { - var targetEntry = GetEntry(targetSeed, targetEntityType, _targetUpdateAdapter); - var targetEntries = targetTableEntryMappingMap.GetOrAddValue(targetEntry); - targetEntries.Add(targetEntry); - } continue; } + Check.DebugAssert(mainSourceTable != null, "mainSourceTable is null"); + var newMapping = true; var sourceTable = diffContext.FindSource(targetTable); if (sourceTable != null) @@ -1853,6 +1839,8 @@ protected virtual Dictionary> DiffData( continue; } + Check.DebugAssert(mainSourceTable != null, "mainSourceTable is null"); + var targetTable = diffContext.FindTarget(sourceTable); var removedMapping = true; if (targetTable != null @@ -1886,20 +1874,26 @@ protected virtual Dictionary> DiffData( continue; } + // If table sharing is being used find the main table of the principal entity type + var mainSourceEntityType = sourceEntityType; + var mainPrincipalSourceTable = mainSourceTable; + while (mainSourceTable.GetRowInternalForeignKeys(mainSourceEntityType).Any()) + { + mainSourceEntityType = mainPrincipalSourceTable.EntityTypeMappings.First(m => m.IsMainEntityTypeMapping).EntityType; + mainPrincipalSourceTable = mainSourceEntityType.GetTableMappings().First(m => m.IsMainTableMapping).Table; + } + foreach (var sourceSeed in sourceEntityType.GetSeedData()) { var sourceEntry = GetEntry(sourceSeed, sourceEntityType, _sourceUpdateAdapter); - if (!_sourceSharedTableEntryMaps.TryGetValue(mainSourceTable, out var sourceTableEntryMappingMap)) + if (!_sourceSharedTableEntryMaps.TryGetValue(mainPrincipalSourceTable, out var sourceTableEntryMappingMap)) { - sourceTableEntryMappingMap = SharedTableEntryMap.CreateSharedTableEntryMapFactory( - mainSourceTable, - _sourceUpdateAdapter) - ((_, __, ___) => new EntryMapping()); - _sourceSharedTableEntryMaps.Add(mainSourceTable, sourceTableEntryMappingMap); + sourceTableEntryMappingMap = new SharedIdentityMap(_sourceUpdateAdapter); + _sourceSharedTableEntryMaps.Add(mainPrincipalSourceTable, sourceTableEntryMappingMap); } - var entryMapping = sourceTableEntryMappingMap.GetOrAddValue(sourceEntry); + var entryMapping = sourceTableEntryMappingMap.GetOrAddValue(sourceEntry, mainSourceTable); entryMapping.SourceEntries.Add(sourceEntry); if (targetKeyMap == null) @@ -1925,28 +1919,28 @@ protected virtual Dictionary> DiffData( } var entry = _targetUpdateAdapter.TryGetEntry(targetKey, targetKeyValues); - if (entry == null - || !_targetSharedTableEntryMaps.TryGetValue(targetTable, out var targetTableEntryMappingMap)) + if (entry == null) { continue; } - foreach (var targetEntry in targetTableEntryMappingMap.GetOrAddValue(entry)) + if (entryMapping.TargetEntries.Add(entry)) { - if (!entryMapping.TargetEntries.Add(targetEntry)) + if (entry.EntityState != EntityState.Added) { + Check.DebugAssert(false, "All entries must be in added state at this point"); continue; } - foreach (var targetProperty in targetEntry.EntityType.GetProperties()) + foreach (var targetProperty in entry.EntityType.GetProperties()) { if (targetProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save) { - targetEntry.SetOriginalValue(targetProperty, targetProperty.ClrType.GetDefaultValue()); + entry.SetOriginalValue(targetProperty, targetProperty.ClrType.GetDefaultValue()); } } - targetEntry.EntityState = EntityState.Unchanged; + entry.EntityState = EntityState.Unchanged; } if (entryMapping.RecreateRow) @@ -2132,7 +2126,6 @@ protected virtual IEnumerable GetDataOperations( } _sourceSharedTableEntryMaps.Clear(); - _targetSharedTableEntryMaps.Clear(); var dataOperations = GetDataOperations(forSource: true, changedTableMappings, entriesWithRemovedMappings, diffContext) .Concat(GetDataOperations(forSource: false, changedTableMappings, entriesWithRemovedMappings, diffContext)); @@ -2221,8 +2214,12 @@ private IEnumerable GetDataOperations( batchInsertOperation = null; } - // There shouldn't be any updates using the source model - Check.DebugAssert(!forSource, "Update using the source model"); + if (forSource) + { + // There shouldn't be any updates using the source model + Check.DebugAssert(false, "Update using the source model"); + break; + } if (command.Entries.Any(en => changedTableMappings.TryGetValue(en.EntityType, out var newTables) && newTables.Any(t => t.Name == command.TableName && t.Schema == command.Schema))) @@ -2486,6 +2483,68 @@ private sealed class EntryMapping public bool RecreateRow { get; set; } } + private sealed class SharedIdentityMap + { + private readonly IUpdateAdapter _updateAdapter; + private readonly Dictionary _entryValueMap + = new Dictionary(); + + public SharedIdentityMap(IUpdateAdapter updateAdapter) + { + _updateAdapter = updateAdapter; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public IEnumerable Values => _entryValueMap.Values; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EntryMapping GetOrAddValue([NotNull] IUpdateEntry entry, ITable table) + { + var mainEntry = GetMainEntry(entry, table); + if (_entryValueMap.TryGetValue(mainEntry, out var entryMapping)) + { + return entryMapping; + } + + entryMapping = new EntryMapping(); + _entryValueMap.Add(mainEntry, entryMapping); + + return entryMapping; + } + + private IUpdateEntry GetMainEntry(IUpdateEntry entry, ITable table) + { + var entityType = entry.EntityType; + var foreignKeys = table.GetRowInternalForeignKeys(entityType); + foreach (var foreignKey in foreignKeys) + { + var principalEntry = _updateAdapter.FindPrincipal(entry, foreignKey); + if (principalEntry != null) + { + return GetMainEntry(principalEntry, table); + } + } + + var mainTable = entry.EntityType.GetTableMappings().First(m => m.IsMainTableMapping).Table; + if (mainTable != table) + { + return GetMainEntry(entry, mainTable); + } + + return entry; + } + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 72f4457a054..3ac46908557 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -757,7 +757,7 @@ protected override ShapedQueryExpression TranslateOfType(ShapedQueryExpression s var derivedType = entityType.GetDerivedTypes().SingleOrDefault(et => et.ClrType == resultType); if (derivedType != null) { - if (!derivedType.GetIsDiscriminatorMappingComplete() + if (!derivedType.GetRootType().GetIsDiscriminatorMappingComplete() || !derivedType.GetAllBaseTypesInclusiveAscending() .All(e => (e == derivedType || e.IsAbstract()) && !HasSiblings(e))) { diff --git a/src/EFCore.Relational/Query/SqlExpressionFactory.cs b/src/EFCore.Relational/Query/SqlExpressionFactory.cs index 0918af8a0a6..3653682d4be 100644 --- a/src/EFCore.Relational/Query/SqlExpressionFactory.cs +++ b/src/EFCore.Relational/Query/SqlExpressionFactory.cs @@ -881,7 +881,7 @@ private SqlExpression GenerateJoinPredicate( private bool AddDiscriminatorCondition(SelectExpression selectExpression, IEntityType entityType) { - if (entityType.GetIsDiscriminatorMappingComplete() + if (entityType.GetRootType().GetIsDiscriminatorMappingComplete() && entityType.GetAllBaseTypesInclusiveAscending() .All(e => (e == entityType || e.IsAbstract()) && !HasSiblings(e))) { diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 6a3dbac6113..dd94684c419 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -39,9 +39,6 @@ public class CommandBatchPreparer : ICommandBatchPreparer private readonly int _minBatchSize; private readonly bool _sensitiveLoggingEnabled; - private IReadOnlyDictionary<(string, string), SharedTableEntryMapFactory> - _sharedTableEntryMapFactories; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -168,12 +165,6 @@ protected virtual IEnumerable CreateModificationCommands( [NotNull] Func generateParameterName) { var commands = new List(); - if (_sharedTableEntryMapFactories == null) - { - _sharedTableEntryMapFactories = SharedTableEntryMap - .CreateSharedTableEntryMapFactories(updateAdapter.Model, updateAdapter); - } - Dictionary<(string Name, string Schema), SharedTableEntryMap> sharedTablesCommandsMap = null; foreach (var entry in entries) @@ -195,23 +186,21 @@ protected virtual IEnumerable CreateModificationCommands( ModificationCommand command; var isMainEntry = true; - if (_sharedTableEntryMapFactories.TryGetValue(tableKey, out var commandIdentityMapFactory)) + if (table.IsShared) { if (sharedTablesCommandsMap == null) { - sharedTablesCommandsMap = - new Dictionary<(string, string), SharedTableEntryMap>(); + sharedTablesCommandsMap = new Dictionary<(string, string), SharedTableEntryMap>(); } if (!sharedTablesCommandsMap.TryGetValue(tableKey, out var sharedCommandsMap)) { - sharedCommandsMap = commandIdentityMapFactory( - (n, s, c) => new ModificationCommand( - n, s, generateParameterName, _sensitiveLoggingEnabled, c)); + sharedCommandsMap = new SharedTableEntryMap(table, updateAdapter); sharedTablesCommandsMap.Add(tableKey, sharedCommandsMap); } - command = sharedCommandsMap.GetOrAddValue(entry); + command = sharedCommandsMap.GetOrAddValue(entry, + (n, s, c) => new ModificationCommand(n, s, generateParameterName, _sensitiveLoggingEnabled, c)); isMainEntry = sharedCommandsMap.IsMainEntry(entry); } else diff --git a/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs b/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs index 758dd415de2..200a7882a4e 100644 --- a/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs +++ b/src/EFCore.Relational/Update/Internal/SharedTableEntryMap.cs @@ -19,11 +19,8 @@ public class SharedTableEntryMap { private readonly ITable _table; private readonly IUpdateAdapter _updateAdapter; - private readonly SharedTableEntryValueFactory _createElement; private readonly IComparer _comparer; - - private readonly Dictionary _entryValueMap - = new Dictionary(); + private readonly Dictionary _entryValueMap = new Dictionary(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -33,57 +30,13 @@ private readonly Dictionary _entryValueMap /// public SharedTableEntryMap( [NotNull] ITable table, - [NotNull] IUpdateAdapter updateAdapter, - [NotNull] SharedTableEntryValueFactory createElement) + [NotNull] IUpdateAdapter updateAdapter) { _table = table; _updateAdapter = updateAdapter; - _createElement = createElement; _comparer = new EntryComparer(table); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static Dictionary<(string Name, string Schema), SharedTableEntryMapFactory> - CreateSharedTableEntryMapFactories( - [NotNull] IModel model, - [NotNull] IUpdateAdapter updateAdapter) - { - var sharedTablesMap = new Dictionary<(string, string), SharedTableEntryMapFactory>(); - foreach (var table in model.GetRelationalModel().Tables) - { - if (!table.IsShared) - { - continue; - } - - var factory = CreateSharedTableEntryMapFactory(table, updateAdapter); - - sharedTablesMap.Add((table.Name, table.Schema), factory); - } - - return sharedTablesMap; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static SharedTableEntryMapFactory CreateSharedTableEntryMapFactory( - [NotNull] ITable table, - [NotNull] IUpdateAdapter updateAdapter) - => createElement - => new SharedTableEntryMap( - table, - updateAdapter, - createElement); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -98,7 +51,7 @@ public static SharedTableEntryMapFactory CreateSharedTableEntryMapFactor /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual TValue GetOrAddValue([NotNull] IUpdateEntry entry) + public virtual TValue GetOrAddValue([NotNull] IUpdateEntry entry, [NotNull] SharedTableEntryValueFactory createElement) { var mainEntry = GetMainEntry(entry); if (_entryValueMap.TryGetValue(mainEntry, out var sharedCommand)) @@ -106,7 +59,7 @@ public virtual TValue GetOrAddValue([NotNull] IUpdateEntry entry) return sharedCommand; } - sharedCommand = _createElement(_table.Name, _table.Schema, _comparer); + sharedCommand = createElement(_table.Name, _table.Schema, _comparer); _entryValueMap.Add(mainEntry, sharedCommand); return sharedCommand; diff --git a/src/EFCore/Extensions/EntityTypeExtensions.cs b/src/EFCore/Extensions/EntityTypeExtensions.cs index 1dc3efd3411..44aae61077a 100644 --- a/src/EFCore/Extensions/EntityTypeExtensions.cs +++ b/src/EFCore/Extensions/EntityTypeExtensions.cs @@ -742,15 +742,8 @@ public static IProperty GetDiscriminatorProperty([NotNull] this IEntityType enti /// /// The entity type. public static bool GetIsDiscriminatorMappingComplete([NotNull] this IEntityType entityType) - { - if (entityType.BaseType != null) - { - return entityType.GetRootType().GetIsDiscriminatorMappingComplete(); - } - - return (bool?)entityType[CoreAnnotationNames.DiscriminatorMappingComplete] + => (bool?)entityType[CoreAnnotationNames.DiscriminatorMappingComplete] ?? GetDefaultIsDiscriminatorMappingComplete(entityType); - } private static bool GetDefaultIsDiscriminatorMappingComplete(IEntityType entityType) => true; diff --git a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs index 3eac4122a31..a2245d61dc1 100644 --- a/src/EFCore/Extensions/MutableEntityTypeExtensions.cs +++ b/src/EFCore/Extensions/MutableEntityTypeExtensions.cs @@ -587,7 +587,7 @@ public static void SetDiscriminatorMappingComplete([NotNull] this IMutableEntity { Check.NotNull(entityType, nameof(entityType)); - entityType.GetRootType().SetOrRemoveAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete, complete); + entityType.SetOrRemoveAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete, complete); } /// diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 830e3256782..57b02642127 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -4051,6 +4051,15 @@ public virtual InternalEntityTypeBuilder HasNoDiscriminator(ConfigurationSource Metadata.SetDiscriminatorProperty(null, configurationSource); + if (configurationSource == ConfigurationSource.Explicit) + { + Metadata.SetDiscriminatorMappingComplete(null); + } + else if (CanSetAnnotation(CoreAnnotationNames.DiscriminatorMappingComplete, null, configurationSource)) + { + Metadata.SetDiscriminatorMappingComplete(null, configurationSource == ConfigurationSource.DataAnnotation); + } + return this; } diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index 62d8b8b4315..c9a1c1d54dd 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -64,6 +64,7 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.ColumnType, RelationalAnnotationNames.TableColumnMappings, RelationalAnnotationNames.ViewColumnMappings, + RelationalAnnotationNames.RelationalOverrides, RelationalAnnotationNames.DefaultValueSql, RelationalAnnotationNames.ComputedColumnSql, RelationalAnnotationNames.DefaultValue, diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 93d967c2ac0..83211a893e7 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -239,6 +239,7 @@ private void AssertAnnotations(IMutableAnnotatable element) && a != RelationalAnnotationNames.ForeignKeyMappings && a != RelationalAnnotationNames.TableIndexMappings && a != RelationalAnnotationNames.UniqueConstraintMappings + && a != RelationalAnnotationNames.RelationalOverrides #pragma warning disable CS0618 // Type or member is obsolete && a != RelationalAnnotationNames.SequencePrefix)) #pragma warning restore CS0618 // Type or member is obsolete diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index c2a815b404f..0ca8145e1a5 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -341,6 +341,52 @@ public virtual void Entities_are_stored_in_model_snapshot() }); } + [ConditionalFact] + public virtual void Entities_are_stored_in_model_snapshot_for_TPT() + { + Test( + builder => + { + builder.Entity() + .ToTable("DerivedEntity"); + builder.Entity(); + }, + AddBoilerPlate( + GetHeading() + + @" + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", b => + { + b.Property(""Id"") + .ValueGeneratedOnAdd() + .HasColumnType(""int"") + .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property(""Discriminator"") + .HasColumnType(""nvarchar(max)""); + + b.HasKey(""Id""); + + b.ToTable(""BaseEntity""); + }); + + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b => + { + b.HasBaseType(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity""); + + b.Property(""Name"") + .HasColumnType(""nvarchar(max)""); + + b.ToTable(""DerivedEntity""); + });"), + o => + { + Assert.Equal(3, o.GetAnnotations().Count()); + + Assert.Equal("DerivedEntity", + o.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity").GetTableName()); + }); + } + [ConditionalFact] public void Views_are_ignored() { @@ -420,7 +466,6 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() builder => { builder.Entity() - .HasBaseType() .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id"); builder.Entity(); }, diff --git a/test/EFCore.Relational.Specification.Tests/Query/InheritanceRelationalFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/InheritanceRelationalFixture.cs index b13adb97d5a..6eefaf50f87 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/InheritanceRelationalFixture.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/InheritanceRelationalFixture.cs @@ -22,6 +22,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); modelBuilder.Entity().HasMany(e => e.Prey).WithOne().HasForeignKey(e => e.EagleId).IsRequired(false); + modelBuilder.Entity().HasDiscriminator().IsComplete(IsDiscriminatorMappingComplete); modelBuilder.Entity().Property(e => e.Species).HasMaxLength(100); modelBuilder.Entity().Property(e => e.Carbonation).HasColumnName("CokeCO2"); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index 4cdb44d9b6b..e166b357126 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -74,7 +74,7 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) var ordersView = orderMapping.View; Assert.Same(ordersView, model.FindView(ordersView.Name, ordersView.Schema)); Assert.Equal( - new[] { "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address", nameof(Order), nameof(OrderDetails) }, + new[] { nameof(Order), "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address", nameof(OrderDetails) }, ordersView.EntityTypeMappings.Select(m => m.EntityType.DisplayName())); Assert.Equal(new[] { nameof(Order.AlternateId), @@ -173,7 +173,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) var ordersTable = orderMapping.Table; Assert.Same(ordersTable, model.FindTable(ordersTable.Name, ordersTable.Schema)); Assert.Equal( - new[] { "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address", nameof(Order), nameof(OrderDetails) }, + new[] { nameof(Order), "OrderDetails.BillingAddress#Address", "OrderDetails.ShippingAddress#Address", nameof(OrderDetails) }, ordersTable.EntityTypeMappings.Select(m => m.EntityType.DisplayName())); Assert.Equal(new[] { nameof(Order.AlternateId), diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 8fe9b9abf78..f00efe3ad17 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -1749,6 +1749,66 @@ public void Rename_property_and_column_when_snapshot() skipSourceConventions: true); } + [ConditionalFact] + public void Rename_column_in_TPT_with_table_sharing_and_seed_data() + { + Execute( + common => { + common.Entity( + "Order", + x => + { + x.ToTable("Order"); + x.Property("Id"); + }); + common.Entity( + "DetailedOrder", + x => + { + x.ToTable("DetailedOrder"); + x.HasBaseType("Order"); + x.Property("Description").HasColumnName("Description"); + x.HasData(new { Id = 42, Description = "Order 1" }); + }); + common.Entity( + "OrderDetails", + x => + { + x.ToTable("DetailedOrder"); + x.Property("Id"); + x.Property("Description").HasColumnName("Description"); + x.Property("OrderDate"); + x.HasOne("DetailedOrder", null).WithOne().HasForeignKey("OrderDetails", "Id"); + x.HasData(new { Id = 42, Description = "Order 1", OrderDate = DateTime.MinValue }); + }); + }, + _ => { }, + target => { + target.Entity( + "DetailedOrder", + x => + { + x.Property("Description").HasColumnName("OrderDescription"); + }); + target.Entity( + "OrderDetails", + x => + { + x.Property("Description").HasColumnName("OrderDescription"); + }); + }, + operations => + { + Assert.Equal(1, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Null(operation.Schema); + Assert.Equal("DetailedOrder", operation.Table); + Assert.Equal("Description", operation.Name); + Assert.Equal("OrderDescription", operation.NewName); + }); + } + private class Crab { public string Id { get; set; } diff --git a/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs b/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs index 13f3b0589a2..001b7f12976 100644 --- a/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/InheritanceTestBase.cs @@ -859,9 +859,11 @@ public void Can_get_set_discriminator_mapping_is_complete() baseTypeBuilder.HasDiscriminator("Discriminator").IsComplete(false); Assert.False(baseTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); + Assert.True(derivedTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); derivedTypeBuilder.HasDiscriminator("Discriminator").IsComplete(true); - Assert.True(baseTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); + Assert.False(baseTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); + Assert.True(derivedTypeBuilder.Metadata.GetIsDiscriminatorMappingComplete()); } protected class L