From da50fcf2ef4e8dd11eedb8719ff780d70c17b7eb Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 3 Mar 2023 10:03:37 +0000 Subject: [PATCH] Handle entity paths with no tables in model differ (#30347) --- .../Internal/MigrationsModelDiffer.cs | 20 +-- .../Migrations/SqlServerModelDifferTest.cs | 140 ++++++++++++++++++ 2 files changed, 151 insertions(+), 9 deletions(-) diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index 81c106a117f..bb3adbeb9ea 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -935,11 +935,13 @@ private static bool ColumnStructureEquals(IColumn source, IColumn target) private static bool EntityTypePathEquals(IEntityType source, IEntityType target, DiffContext diffContext) { - var sourceTable = diffContext.GetTable(source); - var targetTable = diffContext.GetTable(target); + var sourceTable = diffContext.FindTable(source); + var targetTable = diffContext.FindTable(target); - if (sourceTable.EntityTypeMappings.Count() == 1 - && targetTable.EntityTypeMappings.Count() == 1) + if ((sourceTable == null + && targetTable == null) + || (sourceTable?.EntityTypeMappings.Count() == 1 + && targetTable?.EntityTypeMappings.Count() == 1)) { return true; } @@ -949,8 +951,8 @@ private static bool EntityTypePathEquals(IEntityType source, IEntityType target, return false; } - var nextSource = sourceTable.GetRowInternalForeignKeys(source).FirstOrDefault()?.PrincipalEntityType; - var nextTarget = targetTable.GetRowInternalForeignKeys(target).FirstOrDefault()?.PrincipalEntityType; + var nextSource = sourceTable?.GetRowInternalForeignKeys(source).FirstOrDefault()?.PrincipalEntityType; + var nextTarget = targetTable?.GetRowInternalForeignKeys(target).FirstOrDefault()?.PrincipalEntityType; return (nextSource == null && nextTarget == null) || (nextSource != null && nextTarget != null @@ -1512,7 +1514,7 @@ protected virtual IEnumerable Diff( Diff, Add, Remove, - (s, t, c) => c.GetTable(s.EntityType) == c.FindSource(c.GetTable(t.EntityType)) + (s, t, c) => c.FindTable(s.EntityType) == c.FindSource(c.FindTable(t.EntityType)) && string.Equals(s.Name, t.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(s.Sql, t.Sql, StringComparison.OrdinalIgnoreCase)); @@ -2559,8 +2561,8 @@ public virtual void AddDrop(IColumn source, DropColumnOperation operation) /// 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 ITable GetTable(IEntityType entityType) - => entityType.GetTableMappings().First().Table; + public virtual ITable? FindTable(IEntityType entityType) + => entityType.GetTableMappings().FirstOrDefault()?.Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs index 52b288987f0..b99f98fdc9c 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs @@ -250,6 +250,146 @@ public void Alter_column_computation() Assert.Equal("CAST(CURRENT_TIMESTAMP AS int)", operation.ComputedColumnSql); }); + [ConditionalFact] // Issue #30321 + public void Rename_column_TPC() + => Execute( + source => + { + source.Entity( + "Campaign", + x => + { + x.ToTable((string)null); + x.UseTpcMappingStrategy(); + x.Property("Id"); + x.Property("Status"); + }); + + source.Entity( + "SearchCampaign", + x => + { + x.HasBaseType("Campaign"); + }); + }, + source => + { + }, + target => + { + target.Entity( + "Campaign", + x => + { + x.Property("Status").HasColumnName("status_new"); + }); + }, + operations => + { + Assert.Equal(1, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Null(operation.Schema); + Assert.Equal("SearchCampaign", operation.Table); + Assert.Equal("Status", operation.Name); + Assert.Equal("status_new", operation.NewName); + }); + + [ConditionalFact] + public void Rename_column_TPT() + => Execute( + source => + { + source.Entity( + "Campaign", + x => + { + x.UseTptMappingStrategy(); + x.Property("Id"); + x.Property("Status"); + }); + + source.Entity( + "SearchCampaign", + x => + { + x.HasBaseType("Campaign"); + }); + }, + source => + { + }, + target => + { + target.Entity( + "Campaign", + x => + { + x.Property("Status").HasColumnName("status_new"); + }); + }, + operations => + { + Assert.Equal(1, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Null(operation.Schema); + Assert.Equal("Campaign", operation.Table); + Assert.Equal("Status", operation.Name); + Assert.Equal("status_new", operation.NewName); + }); + + + [ConditionalFact] + public void Rename_column_TPC_non_abstract() + => Execute( + source => + { + source.Entity( + "Campaign", + x => + { + x.UseTpcMappingStrategy(); + x.Property("Id"); + x.Property("Status"); + }); + + source.Entity( + "SearchCampaign", + x => + { + x.HasBaseType("Campaign"); + }); + }, + source => + { + }, + target => + { + target.Entity( + "Campaign", + x => + { + x.Property("Status").HasColumnName("status_new"); + }); + }, + operations => + { + Assert.Equal(2, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Null(operation.Schema); + Assert.Equal("SearchCampaign", operation.Table); + Assert.Equal("Status", operation.Name); + Assert.Equal("status_new", operation.NewName); + + operation = Assert.IsType(operations[1]); + Assert.Null(operation.Schema); + Assert.Equal("Campaign", operation.Table); + Assert.Equal("Status", operation.Name); + Assert.Equal("status_new", operation.NewName); + }); + [ConditionalFact] public void Alter_primary_key_clustering() => Execute(