Skip to content

Commit 896f4e1

Browse files
authored
Configure the correct end as principal for self-ref collection with [ForeignKey]
Fixes #26364
1 parent ab8a369 commit 896f4e1

File tree

4 files changed

+80
-4
lines changed

4 files changed

+80
-4
lines changed

src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ public virtual void ProcessEntityTypeAdded(
116116
foreach (var navigation in foreignKeyNavigations)
117117
{
118118
entityTypeBuilder.HasRelationship(
119-
entityType, foreignKeyNavigations[0], setTargetAsPrincipal: true, fromDataAnnotation: true);
119+
entityType,
120+
navigation,
121+
setTargetAsPrincipal: navigation.GetMemberType().IsAssignableFrom(entityType.ClrType),
122+
fromDataAnnotation: true);
120123
}
121124
}
122125

src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2823,16 +2823,30 @@ private static InternalIndexBuilder DetachIndex(Index indexToDetach)
28232823
"required should only be set if principal end is known");
28242824

28252825
var navigationProperty = navigationToTarget?.MemberInfo;
2826+
if (navigationProperty == null
2827+
&& navigationToTarget?.Name != null
2828+
&& !Metadata.IsPropertyBag)
2829+
{
2830+
navigationProperty = InternalForeignKeyBuilder.FindCompatibleClrMember(
2831+
navigationToTarget!.Value.Name!, Metadata, targetEntityType,
2832+
shouldThrow: configurationSource == ConfigurationSource.Explicit);
2833+
if (navigationProperty != null)
2834+
{
2835+
navigationToTarget = MemberIdentity.Create(navigationProperty);
2836+
}
2837+
}
2838+
28262839
var inverseProperty = inverseNavigation?.MemberInfo;
28272840
if (setTargetAsPrincipal == false
2828-
|| (inverseNavigation == null
2841+
|| (setTargetAsPrincipal == null
2842+
&& inverseNavigation?.Name == null
28292843
&& navigationProperty?.GetMemberType().IsAssignableFrom(
28302844
targetEntityType.ClrType)
28312845
== false))
28322846
{
2833-
// Target is expected to be dependent or only one nav specified and it can't be the nav to principal
2847+
// Target is dependent or only one nav specified and it can't be the nav to principal
28342848
return targetEntityType.Builder.HasRelationship(
2835-
Metadata, null, navigationToTarget, !setTargetAsPrincipal, configurationSource, required);
2849+
Metadata, inverseNavigation, navigationToTarget, setTargetAsPrincipal: true, configurationSource, required);
28362850
}
28372851

28382852
if (setTargetAsPrincipal == null

test/EFCore.Specification.Tests/DataAnnotationTestBase.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,48 @@ public class ProfileDetails12
14291429
public int Id { get; set; }
14301430
}
14311431

1432+
[ConditionalFact]
1433+
public virtual void Inverse_and_self_ref_ForeignKey()
1434+
{
1435+
var modelBuilder = CreateModelBuilder();
1436+
1437+
modelBuilder.Entity<MenuGroup>();
1438+
1439+
var model = Validate(modelBuilder);
1440+
1441+
var menuGroup = model.FindEntityType(typeof(MenuGroup));
1442+
var groupsNavigation = menuGroup.FindNavigation(nameof(MenuGroup.Groups));
1443+
Assert.Equal(nameof(MenuGroup.FkGroup), groupsNavigation.ForeignKey.Properties.Single().Name);
1444+
1445+
var pagesNavigation = menuGroup.FindNavigation(nameof(MenuGroup.Pages));
1446+
Assert.Equal(nameof(MenuPage.FkGroupNavigation), pagesNavigation.Inverse.Name);
1447+
Assert.Equal(nameof(MenuPage.FkGroup), pagesNavigation.ForeignKey.Properties.Single().Name);
1448+
}
1449+
1450+
protected class MenuGroup
1451+
{
1452+
public Guid Id { get; set; }
1453+
public Guid? FkGroup { get; set; }
1454+
1455+
[InverseProperty(nameof(MenuPage.FkGroupNavigation))]
1456+
public virtual ICollection<MenuPage> Pages { get; set; }
1457+
1458+
[ForeignKey(nameof(FkGroup))]
1459+
public virtual ICollection<MenuGroup> Groups { get; set; }
1460+
}
1461+
1462+
protected class MenuPage
1463+
{
1464+
public Guid Id { get; set; }
1465+
1466+
public Guid? FkGroup { get; set; }
1467+
1468+
[ForeignKey(nameof(FkGroup))]
1469+
[InverseProperty(nameof(MenuGroup.Pages))]
1470+
public virtual MenuGroup FkGroupNavigation { get; set; }
1471+
}
1472+
1473+
14321474
[ConditionalFact]
14331475
public virtual void Multiple_self_ref_ForeignKeys_on_navigations()
14341476
{

test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,23 @@ public void Can_add_relationship_if_principal_entity_has_no_PK()
7272
Assert.True(fkProperty.IsShadowProperty());
7373
}
7474

75+
[ConditionalFact]
76+
public void Collection_navigation_to_principal_throws()
77+
{
78+
var modelBuilder = CreateModelBuilder();
79+
var principalEntityBuilder = modelBuilder.Entity(typeof(Customer), ConfigurationSource.Explicit);
80+
var dependentEntityBuilder = modelBuilder.Entity(typeof(Order), ConfigurationSource.Explicit);
81+
82+
Assert.Equal(CoreStrings.PrincipalEndIncompatibleNavigations(
83+
nameof(Customer) + "." + nameof(Customer.Orders), nameof(Order), nameof(Order)),
84+
Assert.Throws<InvalidOperationException>(() =>
85+
principalEntityBuilder.HasRelationship(
86+
dependentEntityBuilder.Metadata,
87+
Customer.OrdersProperty,
88+
ConfigurationSource.DataAnnotation,
89+
targetIsPrincipal: true)).Message);
90+
}
91+
7592
[ConditionalFact]
7693
public void Can_add_relationship_if_principal_entity_PK_name_contains_principal_entity_name()
7794
{

0 commit comments

Comments
 (0)