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;