Skip to content

Commit

Permalink
Add exception message for a shadow skip navigation
Browse files Browse the repository at this point in the history
Fixes #23362
  • Loading branch information
AndriySvyryd committed Sep 21, 2021
1 parent b8e147f commit 1c3a0b5
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 5 deletions.
10 changes: 10 additions & 0 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ protected virtual void ValidateRelationships(
CoreStrings.SkipNavigationNoInverse(
skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName()));
}

if (skipNavigation.IsShadowProperty())
{
throw new InvalidOperationException(
CoreStrings.ShadowManyToManyNavigation(
skipNavigation.DeclaringEntityType.DisplayName(),
skipNavigation.Name,
skipNavigation.Inverse.DeclaringEntityType.DisplayName(),
skipNavigation.Inverse.Name));
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ public virtual ReferenceCollectionBuilder<TEntity, TRelatedEntity> WithOne(
/// <returns> An object to further configure the relationship. </returns>
public new virtual CollectionCollectionBuilder<TRelatedEntity, TEntity> WithMany(string navigationName)
{
if (Builder != null
&& Builder.Metadata.PrincipalToDependent == null)
{
throw new InvalidOperationException(
CoreStrings.MissingInverseManyToManyNavigation(
Builder.Metadata.PrincipalEntityType.DisplayName(),
Builder.Metadata.DeclaringEntityType.DisplayName()));
}

var leftName = Builder?.Metadata.PrincipalToDependent!.Name;
var collectionCollectionBuilder =
new CollectionCollectionBuilder<TRelatedEntity, TEntity>(
Expand Down
16 changes: 12 additions & 4 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,7 @@
<value>The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'.</value>
</data>
<data name="MissingInverseManyToManyNavigation" xml:space="preserve">
<value>Unable to set up a many-to-many relationship between the entity types '{principalEntityType}' and '{declaringEntityType}' because one of the navigations was not specified. Provide a navigation in the 'HasMany' call in 'OnModelCreating'.</value>
<value>Unable to set up a many-to-many relationship between the entity types '{principalEntityType}' and '{declaringEntityType}' because one of the navigations was not specified. Provide a navigation in the 'HasMany' call in 'OnModelCreating'. Consider adding a private property for this.</value>
</data>
<data name="ModelMutable" xml:space="preserve">
<value>Runtime metadata changes are not allowed when the model hasn't been marked as read-only.</value>
Expand Down Expand Up @@ -1437,6 +1437,9 @@
<value>The entity type '{entityType}' is in shadow state. A valid model requires all entity types to have a corresponding CLR type.</value>
<comment>Obsolete</comment>
</data>
<data name="ShadowManyToManyNavigation" xml:space="preserve">
<value>Unable to set up a many-to-many relationship between '{leftEntityType}.{leftNavigation}' and '{rightEntityType}.{rightNavigation}' because one or both of the navigations doesn't have a corresponding CLR property. Consider adding a corresponding private property to the entity CLR type.</value>
</data>
<data name="SharedTypeDerivedType" xml:space="preserve">
<value>The shared-type entity type '{entityType}' cannot have a base type.</value>
</data>
Expand Down
22 changes: 22 additions & 0 deletions test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,28 @@ public virtual void Throws_for_many_to_many_with_only_one_navigation_configured(
.WithMany(d => d.ManyToManyPrincipals)).Message);
}

[ConditionalFact]
public virtual void Throws_for_many_to_many_with_a_shadow_navigation()
{
var modelBuilder = CreateModelBuilder();

modelBuilder.Ignore<OneToOneNavPrincipal>();
modelBuilder.Entity<NavDependent>().Ignore(d => d.ManyToManyPrincipals);

modelBuilder.Entity<ManyToManyNavPrincipal>()
.HasMany(d => d.Dependents)
.WithMany("Shadow");

Assert.Equal(
CoreStrings.ShadowManyToManyNavigation(
nameof(NavDependent),
"Shadow",
nameof(ManyToManyNavPrincipal),
nameof(ManyToManyNavPrincipal.Dependents)),
Assert.Throws<InvalidOperationException>(
() => modelBuilder.FinalizeModel()).Message);
}

[ConditionalFact]
public virtual void Throws_for_self_ref_with_same_navigation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ protected override TestModelBuilder CreateTestModelBuilder(TestHelpers testHelpe
=> new GenericStringTestModelBuilder(testHelpers, configure);
}

public class GenericManyToManyString : ManyToManyTestBase
{
protected override TestModelBuilder CreateTestModelBuilder(TestHelpers testHelpers, Action<ModelConfigurationBuilder>? configure)
=> new GenericStringTestModelBuilder(testHelpers, configure);
}

public class GenericOneToOneString : OneToOneTestBase
{
protected override TestModelBuilder CreateTestModelBuilder(TestHelpers testHelpers, Action<ModelConfigurationBuilder>? configure)
Expand Down

0 comments on commit 1c3a0b5

Please sign in to comment.