diff --git a/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs b/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs index ce8447997c4..254aad86412 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CandidateNamingService.cs @@ -152,9 +152,23 @@ private static string FindCommonPrefix(string firstName, IEnumerable pro private static string StripId(string commonPrefix) { - return commonPrefix.Length > 2 - && commonPrefix.EndsWith("id", StringComparison.OrdinalIgnoreCase) - ? commonPrefix[..^2] + if (commonPrefix.Length < 3 + || !commonPrefix.EndsWith("id", StringComparison.OrdinalIgnoreCase)) + { + return commonPrefix; + } + + int i; + for (i = commonPrefix.Length - 3; i >= 0; i--) + { + if (char.IsLetterOrDigit(commonPrefix[i])) + { + break; + } + } + + return i != 0 + ? commonPrefix.Substring(0, i + 1) : commonPrefix; } } diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 9177d526226..1ded59c4e27 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -92,7 +92,7 @@ public virtual IModel Create(DatabaseModel databaseModel, ModelReverseEngineerOp ? (Func)(t => t.Name) : t => _candidateNamingService.GenerateCandidateIdentifier(t), _cSharpUtilities, - options.UseDatabaseNames || options.NoPluralize + options.NoPluralize ? (Func)null : _pluralizer.Singularize); _dbSetNamer = new CSharpUniqueNamer( @@ -100,7 +100,7 @@ public virtual IModel Create(DatabaseModel databaseModel, ModelReverseEngineerOp ? (Func)(t => t.Name) : t => _candidateNamingService.GenerateCandidateIdentifier(t), _cSharpUtilities, - options.UseDatabaseNames || options.NoPluralize + options.NoPluralize ? (Func)null : _pluralizer.Pluralize); _columnNamers = new Dictionary>(); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs index ef385e75b6d..57c7af46d9c 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs @@ -1668,5 +1668,131 @@ public void Column_and_table_comments() var column = model.FindEntityType("Table").GetProperty("Column"); Assert.Equal("An int column", column.GetComment()); } + + [ConditionalTheory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + [InlineData(true, true, true)] + public void UseDatabaseNames_and_NoPluralize_work_together( + bool useDatabaseNames, + bool noPluralize, + bool pluralTables) + { + var userTableName = pluralTables ? "users" : "user"; + var postTableName = pluralTables ? "posts" : "post"; + var databaseModel = new DatabaseModel + { + Tables = + { + new DatabaseTable + { + Name = userTableName, + Columns = + { + new DatabaseColumn + { + Name = "id", + StoreType = "int" + } + }, + PrimaryKey = new DatabasePrimaryKey + { + Columns = { new DatabaseColumnRef("id") } + } + }, + new DatabaseTable + { + Name = postTableName, + Columns = + { + new DatabaseColumn + { + Name = "id", + StoreType = "int" + }, + new DatabaseColumn + { + Name = "author_id", + StoreType = "int" + } + }, + PrimaryKey = new DatabasePrimaryKey + { + Columns = { new DatabaseColumnRef("id") } + }, + ForeignKeys = + { + new DatabaseForeignKey + { + PrincipalTable = new DatabaseTableRef(userTableName), + Columns = { new DatabaseColumnRef("author_id") }, + PrincipalColumns = { new DatabaseColumnRef("id") } + } + } + } + } + }; + + var model = _factory.Create( + databaseModel, + new ModelReverseEngineerOptions { UseDatabaseNames = useDatabaseNames, NoPluralize = noPluralize }); + + var user = Assert.Single(model.GetEntityTypes().Where(e => e.GetTableName() == userTableName)); + var id = Assert.Single(user.GetProperties().Where(p => p.GetColumnName() == "id")); + var foreignKey = Assert.Single(user.GetReferencingForeignKeys()); + if (useDatabaseNames && noPluralize) + { + Assert.Equal(userTableName, user.Name); + Assert.Equal(userTableName, user[ScaffoldingAnnotationNames.DbSetName]); + Assert.Equal("id", id.Name); + Assert.Equal(postTableName, foreignKey.PrincipalToDependent.Name); + Assert.Equal("author_id", Assert.Single(foreignKey.Properties).Name); + Assert.Equal("author", foreignKey.DependentToPrincipal.Name); + } + else if (useDatabaseNames) + { + Assert.Equal("user", user.Name); + Assert.Equal("users", user[ScaffoldingAnnotationNames.DbSetName]); + Assert.Equal("id", id.Name); + Assert.Equal("posts", foreignKey.PrincipalToDependent.Name); + Assert.Equal("author_id", Assert.Single(foreignKey.Properties).Name); + Assert.Equal("author", foreignKey.DependentToPrincipal.Name); + } + else if (noPluralize) + { + if (pluralTables) + { + Assert.Equal("Users", user.Name); + Assert.Equal("Users", user[ScaffoldingAnnotationNames.DbSetName]); + Assert.Equal("Id", id.Name); + Assert.Equal("Posts", foreignKey.PrincipalToDependent.Name); + Assert.Equal("AuthorId", Assert.Single(foreignKey.Properties).Name); + Assert.Equal("Author", foreignKey.DependentToPrincipal.Name); + } + else + { + Assert.Equal("User", user.Name); + Assert.Equal("User", user[ScaffoldingAnnotationNames.DbSetName]); + Assert.Equal("Id", id.Name); + Assert.Equal("Post", foreignKey.PrincipalToDependent.Name); + Assert.Equal("AuthorId", Assert.Single(foreignKey.Properties).Name); + Assert.Equal("Author", foreignKey.DependentToPrincipal.Name); + } + } + else + { + Assert.Equal("User", user.Name); + Assert.Equal("Users", user[ScaffoldingAnnotationNames.DbSetName]); + Assert.Equal("Id", id.Name); + Assert.Equal("Posts", foreignKey.PrincipalToDependent.Name); + Assert.Equal("AuthorId", Assert.Single(foreignKey.Properties).Name); + Assert.Equal("Author", foreignKey.DependentToPrincipal.Name); + } + } } } diff --git a/test/EFCore.Design.Tests/TestUtilities/DatabaseColumnRef.cs b/test/EFCore.Design.Tests/TestUtilities/DatabaseColumnRef.cs new file mode 100644 index 00000000000..391a79ee73a --- /dev/null +++ b/test/EFCore.Design.Tests/TestUtilities/DatabaseColumnRef.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; + +namespace Microsoft.EntityFrameworkCore.TestUtilities +{ + class DatabaseColumnRef : DatabaseColumn + { + public DatabaseColumnRef(string name) + { + Name = name; + } + + public override DatabaseTable Table + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override bool IsNullable + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override string StoreType + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override string DefaultValueSql + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override string ComputedColumnSql + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override string Comment + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override ValueGenerated? ValueGenerated + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + } +} diff --git a/test/EFCore.Design.Tests/TestUtilities/DatabaseTableRef.cs b/test/EFCore.Design.Tests/TestUtilities/DatabaseTableRef.cs new file mode 100644 index 00000000000..384e8452d4e --- /dev/null +++ b/test/EFCore.Design.Tests/TestUtilities/DatabaseTableRef.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; + +namespace Microsoft.EntityFrameworkCore.TestUtilities +{ + class DatabaseTableRef : DatabaseTable + { + public DatabaseTableRef(string name, string schema = null) + { + Name = name; + Schema = schema; + } + + public override DatabaseModel Database + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override string Comment + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override DatabasePrimaryKey PrimaryKey + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public override IList Columns + => throw new NotImplementedException(); + + public override IList UniqueConstraints + => throw new NotImplementedException(); + + public override IList Indexes + => throw new NotImplementedException(); + + public override IList ForeignKeys + => throw new NotImplementedException(); + } +} diff --git a/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs b/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs index 6fbeae95790..7e8e9136405 100644 --- a/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs +++ b/test/EFCore.Design.Tests/TestUtilities/FakeScaffoldingModelFactory.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Linq; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; @@ -64,6 +65,12 @@ public override IModel Create(DatabaseModel databaseModel, ModelReverseEngineerO foreignKey.Table = table; FixupColumns(table, foreignKey.Columns); + if (foreignKey.PrincipalTable is DatabaseTableRef tableRef) + { + foreignKey.PrincipalTable = databaseModel.Tables + .First(t => t.Name == tableRef.Name && t.Schema == tableRef.Schema); + } + FixupColumns(foreignKey.PrincipalTable, foreignKey.PrincipalColumns); } } @@ -75,6 +82,11 @@ private static void FixupColumns(DatabaseTable table, IList colu { for (var i = 0; i < columns.Count; i++) { + if (columns[i] is DatabaseColumnRef columnRef) + { + columns[i] = table.Columns.First(c => c.Name == columnRef.Name); + } + columns[i].Table = table; } }