diff --git a/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs b/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs index 9b752e43526..8ff07b260a9 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs @@ -104,39 +104,21 @@ public virtual string GenerateCandidateIdentifier(string originalIdentifier) private static string FindCandidateNavigationName(IEnumerable properties) { - var count = properties.Count(); - if (count == 0) + var name = ""; + foreach (var property in properties) { - return string.Empty; - } - - var firstProperty = properties.First(); - return StripId( - count == 1 - ? firstProperty.Name - : FindCommonPrefix(firstProperty.Name, properties.Select(p => p.Name))); - } - - private static string FindCommonPrefix(string firstName, IEnumerable propertyNames) - { - var prefixLength = 0; - foreach (var c in firstName) - { - foreach (var s in propertyNames) + if (name != "") { - if (s.Length <= prefixLength - || s[prefixLength] != c) - { - return firstName[..prefixLength]; - } + return ""; } - prefixLength++; + name = property.Name; } - return firstName[..prefixLength]; + return StripId(name); } + private static string StripId(string commonPrefix) { if (commonPrefix.Length < 3 diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs index abbd3bea032..7b478be7d58 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs @@ -2600,4 +2600,231 @@ public void Composite_fk_property_ending_in_guid_navigation_name() } ); } + + [ConditionalFact] + public void Unusual_navigation_name() // Issue #14278 + { + var bookDetailsTable = new DatabaseTable + { + Database = Database, + Name = "Book_Details" + }; + + bookDetailsTable.Columns.Add(new DatabaseColumn + { + Table = bookDetailsTable, + Name = "ID", + StoreType = "int" + }); + + bookDetailsTable.Columns.Add(new DatabaseColumn + { + Table = bookDetailsTable, + Name = "Book_Name", + StoreType = "nvarchar(50)" + }); + + bookDetailsTable.Columns.Add(new DatabaseColumn + { + Table = bookDetailsTable, + Name = "Student_Id", + StoreType = "int" + }); + + bookDetailsTable.PrimaryKey = new DatabasePrimaryKey + { + Table = bookDetailsTable, + Name = "PK_Book_Details", + Columns = { bookDetailsTable.Columns.Single(c => c.Name == "ID") } + }; + + var studentDetailsTable = new DatabaseTable + { + Database = Database, + Name = "Student_Details" + }; + + studentDetailsTable.Columns.Add(new DatabaseColumn + { + Table = studentDetailsTable, + Name = "ID", + StoreType = "int" + }); + + studentDetailsTable.Columns.Add(new DatabaseColumn + { + Table = studentDetailsTable, + Name = "Student_Name", + StoreType = "nvarchar(256)" + }); + + studentDetailsTable.PrimaryKey = new DatabasePrimaryKey + { + Table = studentDetailsTable, + Name = "PK_Student_Details", + Columns = { studentDetailsTable.Columns.Single(c => c.Name == "ID") } + }; + + bookDetailsTable.ForeignKeys.Add( + new DatabaseForeignKey + { + Table = bookDetailsTable, + Name = "FK_Foo", + Columns = { bookDetailsTable.Columns.Single(c => c.Name == "Student_Id") }, + PrincipalTable = studentDetailsTable, + PrincipalColumns = { studentDetailsTable.Columns.Single(c => c.Name == "ID") }, + OnDelete = ReferentialAction.Cascade + }); + + var info = new DatabaseModel { Tables = { bookDetailsTable, studentDetailsTable } }; + + var model = _factory.Create(info, new ModelReverseEngineerOptions()); + + Assert.Collection( + model.GetEntityTypes().OrderBy(t => t.Name).Cast(), + entity => + { + Assert.Equal("BookDetail", entity.Name); + Assert.Equal("Student", entity.GetNavigations().Single().Name); + }, + entity => + { + Assert.Equal("StudentDetail", entity.Name); + Assert.Equal("BookDetails", entity.GetNavigations().Single().Name); + } + ); + + model = _factory.Create(info, new ModelReverseEngineerOptions { UseDatabaseNames = true }); + + Assert.Collection( + model.GetEntityTypes().OrderBy(t => t.Name).Cast(), + entity => + { + Assert.Equal("Book_Detail", entity.Name); + Assert.Equal("Student", entity.GetNavigations().Single().Name); + }, + entity => + { + Assert.Equal("Student_Detail", entity.Name); + Assert.Equal("Book_Details", entity.GetNavigations().Single().Name); + } + ); + } + + [ConditionalFact] + public void Interesting_navigation_name() // Issue #27832 + { + var seasonTable = new DatabaseTable { Database = Database, Name = "TmTvSeason" }; + + seasonTable.Columns.Add( + new DatabaseColumn + { + Table = seasonTable, + Name = "Id", + StoreType = "int" + }); + + seasonTable.Columns.Add( + new DatabaseColumn + { + Table = seasonTable, + Name = "ShowId", + StoreType = "int" + }); + + seasonTable.Columns.Add( + new DatabaseColumn + { + Table = seasonTable, + Name = "Name", + StoreType = "nvarchar(300)" + }); + + seasonTable.PrimaryKey = new DatabasePrimaryKey + { + Table = seasonTable, + Name = "PK_TmTvSeason", + Columns = { seasonTable.Columns.Single(c => c.Name == "ShowId"), seasonTable.Columns.Single(c => c.Name == "Id") } + }; + + var episodeTable = new DatabaseTable { Database = Database, Name = "TmTvEpisode" }; + + episodeTable.Columns.Add( + new DatabaseColumn + { + Table = episodeTable, + Name = "Id", + StoreType = "int" + }); + + episodeTable.Columns.Add( + new DatabaseColumn + { + Table = episodeTable, + Name = "SeasonId", + StoreType = "int" + }); + + episodeTable.Columns.Add( + new DatabaseColumn + { + Table = episodeTable, + Name = "ShowId", + StoreType = "int" + }); + + episodeTable.Columns.Add( + new DatabaseColumn + { + Table = episodeTable, + Name = "Name", + StoreType = "nvarchar(300)" + }); + + episodeTable.PrimaryKey = new DatabasePrimaryKey + { + Table = episodeTable, + Name = "PK_TmTvEpisode", + Columns = + { + episodeTable.Columns.Single(c => c.Name == "ShowId"), + episodeTable.Columns.Single(c => c.Name == "SeasonId"), + episodeTable.Columns.Single(c => c.Name == "Id") + } + }; + + episodeTable.ForeignKeys.Add( + new DatabaseForeignKey + { + Table = episodeTable, + Name = "FK_TmTvEpisode_TmTvSeason", + Columns = { episodeTable.Columns.Single(c => c.Name == "ShowId"), episodeTable.Columns.Single(c => c.Name == "SeasonId") }, + PrincipalTable = seasonTable, + PrincipalColumns = { seasonTable.Columns.Single(c => c.Name == "ShowId"), seasonTable.Columns.Single(c => c.Name == "Id") }, + OnDelete = ReferentialAction.Cascade + }); + + var info = new DatabaseModel { Tables = { seasonTable, episodeTable } }; + + var model = _factory.Create(info, new ModelReverseEngineerOptions()); + AssertNavigations(); + + model = _factory.Create(info, new ModelReverseEngineerOptions { UseDatabaseNames = true }); + AssertNavigations(); + + void AssertNavigations() + => Assert.Collection( + model.GetEntityTypes().OrderBy(t => t.Name).Cast(), + entity => + { + Assert.Equal("TmTvEpisode", entity.Name); + Assert.Equal("TmTvSeason", entity.GetNavigations().Single().Name); + }, + entity => + { + Assert.Equal("TmTvSeason", entity.Name); + Assert.Equal("TmTvEpisodes", entity.GetNavigations().Single().Name); + } + ); + } }