diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index ff97982778b..2e48b39fcf3 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -487,7 +487,7 @@ private void Create(IStoredProcedureParameter parameter, CSharpRuntimeAnnotation { var code = Dependencies.CSharpHelper; var mainBuilder = parameters.MainBuilder; - var parameterVariable = code.Identifier(parameter.Name, parameters.ScopeVariables, capitalize: false); + var parameterVariable = code.Identifier(parameter.PropertyName ?? parameter.Name, parameters.ScopeVariables, capitalize: false); mainBuilder .Append("var ").Append(parameterVariable).Append(" = ") diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index d3261e4d04e..8922b2449aa 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -160,7 +160,30 @@ public static string GetDefaultColumnBaseName(this IReadOnlyProperty property) /// The property. /// The default base column name to which the property would be mapped. public static string GetDefaultColumnName(this IReadOnlyProperty property) - => Uniquifier.Truncate(property.Name, property.DeclaringEntityType.Model.GetMaxIdentifierLength()); + { + var name = property.Name; + if (property.IsShadowProperty() + && property.GetContainingForeignKeys().Count() == 1) + { + var foreignKey = property.GetContainingForeignKeys().First(); + var principalEntityType = foreignKey.PrincipalEntityType; + if (!principalEntityType.HasSharedClrType + && principalEntityType.ClrType.IsConstructedGenericType + && foreignKey.DependentToPrincipal == null) + { + var principalProperty = property.FindFirstPrincipal()!; + var principalName = principalEntityType.ShortName(); + if (property.Name.Length == (principalName.Length + principalProperty.Name.Length) + && property.Name.StartsWith(principalName, StringComparison.Ordinal) + && property.Name.EndsWith(principalProperty.Name, StringComparison.Ordinal)) + { + name = principalEntityType.ClrType.ShortDisplayName() + principalProperty.Name; + } + } + } + + return Uniquifier.Truncate(name, property.DeclaringEntityType.Model.GetMaxIdentifierLength()); + } /// /// Returns the default column name to which the property would be mapped. @@ -213,7 +236,7 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) entityType = ownerType; } - var baseName = property.GetDefaultColumnName(); + var baseName = storeObject.StoreObjectType == StoreObjectType.Table ? property.GetDefaultColumnName() : property.Name; if (builder == null) { return baseName; diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs index f6bcac84a17..01981385a9f 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs @@ -43,7 +43,7 @@ public InternalStoredProcedureParameterBuilder( } Metadata.SetName(name, configurationSource); - + return this; } @@ -58,7 +58,7 @@ public virtual bool CanSetName( ConfigurationSource configurationSource) => configurationSource.Overrides(Metadata.GetNameConfigurationSource()) || Metadata.Name == name; - + /// /// 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 @@ -75,7 +75,7 @@ public virtual bool CanSetName( } Metadata.SetDirection(direction, configurationSource); - + return this; } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 3d3951627f0..3d8c260f599 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2614,9 +2614,9 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", false); var principalDerivedId = insertSproc.AddParameter( ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", false); - var baseId = insertSproc.AddParameter( + var id = insertSproc.AddParameter( ""BaseId"", ParameterDirection.Output, false, ""Id"", false); - baseId.AddAnnotation(""foo"", ""bar""); + id.AddAnnotation(""foo"", ""bar""); insertSproc.AddAnnotation(""foo"", ""bar1""); runtimeEntityType.AddAnnotation(""Relational:InsertStoredProcedure"", insertSproc); @@ -2626,7 +2626,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) ""TPC"", true); - var id = deleteSproc.AddParameter( + var id0 = deleteSproc.AddParameter( ""Id"", ParameterDirection.Input, false, ""Id"", false); runtimeEntityType.AddAnnotation(""Relational:DeleteStoredProcedure"", deleteSproc); @@ -2640,7 +2640,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) ""PrincipalBaseId"", ParameterDirection.Input, false, ""PrincipalBaseId"", false); var principalDerivedId0 = updateSproc.AddParameter( ""PrincipalDerivedId"", ParameterDirection.Input, false, ""PrincipalDerivedId"", false); - var id0 = updateSproc.AddParameter( + var id1 = updateSproc.AddParameter( ""Id"", ParameterDirection.Input, false, ""Id"", false); runtimeEntityType.AddAnnotation(""Relational:UpdateStoredProcedure"", updateSproc); diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index b8329394064..acaf3dd2a51 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -545,6 +545,62 @@ public class DisjointChildSubclass2 : Child public abstract class SqlServerOneToMany : RelationalOneToManyTestBase { + [ConditionalFact] + public virtual void Shadow_foreign_keys_to_generic_types_have_terrible_names_that_should_not_change() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().ToTable("Events"); + modelBuilder.Entity>().ToTable("CompanyActivities"); + modelBuilder.Entity>().ToTable("UserActivities"); + + var model = modelBuilder.FinalizeModel(); + + var companyActivityEventType = model.FindEntityType(typeof(ActivityEvent))!; + var eventTable = StoreObjectIdentifier.Create(companyActivityEventType, StoreObjectType.Table)!.Value; + var companyActivityEventFk = companyActivityEventType.GetForeignKeys().Single(); + var companyActivityEventFkProperty = companyActivityEventFk.Properties.Single(); + Assert.Equal("ActivityId", companyActivityEventFkProperty.GetColumnName(eventTable)); + Assert.Equal("FK_Events_CompanyActivities_ActivityId", companyActivityEventFk.GetConstraintName()); + Assert.Equal( + "FK_Events_CompanyActivities_ActivityId", companyActivityEventFk.GetConstraintName( + eventTable, + StoreObjectIdentifier.Create(companyActivityEventFk.PrincipalEntityType, StoreObjectType.Table)!.Value)); + + var userActivityEventType = model.FindEntityType(typeof(ActivityEvent))!; + var userActivityEventFk = userActivityEventType.GetForeignKeys().Single(); + var userActivityEventFkProperty = userActivityEventFk.Properties.Single(); + Assert.Equal("ActivityId", userActivityEventFkProperty.GetColumnName(eventTable)); + Assert.Equal("FK_Events_UserActivities_ActivityId", userActivityEventFk.GetConstraintName()); + Assert.Equal( + "FK_Events_UserActivities_ActivityId", userActivityEventFk.GetConstraintName( + eventTable, + StoreObjectIdentifier.Create(userActivityEventFk.PrincipalEntityType, StoreObjectType.Table)!.Value)); + } + + protected abstract class EventBase + { + public string? Id { get; set; } + } + + protected class Activity + { + public string? Id { get; set; } + public virtual List> Events { get; private set; } = null!; + } + + protected class ActivityEvent : EventBase + { + } + + protected class Company + { + } + + protected class User + { + } + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); } diff --git a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs index aced69e7600..d28257dee91 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - - // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding;