Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/8.0] Throw for conflicting explicit relationships when configuring an entity type as keyless. #32032

Merged
merged 1 commit into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,12 +433,61 @@ public static (InternalKeyBuilder, ConfigurationSource?) DetachKey(Key keyToDeta
{
foreach (var foreignKey in Metadata.GetReferencingForeignKeys().ToList())
{
foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, configurationSource);
if (foreignKey.GetConfigurationSource() != ConfigurationSource.Explicit
|| configurationSource != ConfigurationSource.Explicit)
{
foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, configurationSource);
continue;
}

if (foreignKey.DependentToPrincipal != null && foreignKey.GetDependentToPrincipalConfigurationSource() == ConfigurationSource.Explicit)
{
throw new InvalidOperationException(
CoreStrings.NavigationToKeylessType(foreignKey.DependentToPrincipal.Name, Metadata.DisplayName()));
}
else if ((foreignKey.IsUnique || foreignKey.GetIsUniqueConfigurationSource() != ConfigurationSource.Explicit)
&& foreignKey.GetPrincipalEndConfigurationSource() != ConfigurationSource.Explicit
&& foreignKey.Builder.CanSetEntityTypes(
foreignKey.DeclaringEntityType,
foreignKey.PrincipalEntityType,
configurationSource,
out _,
out var shouldResetToDependent)
&& (!shouldResetToDependent || foreignKey.GetPrincipalToDependentConfigurationSource() != ConfigurationSource.Explicit))
{
foreignKey.Builder.HasEntityTypes(
foreignKey.DeclaringEntityType,
foreignKey.PrincipalEntityType,
configurationSource);
}
else
{
throw new InvalidOperationException(
CoreStrings.PrincipalKeylessType(
Metadata.DisplayName(),
Metadata.DisplayName()
+ (foreignKey.PrincipalToDependent == null
? ""
: "." + foreignKey.PrincipalToDependent.Name),
foreignKey.DeclaringEntityType.DisplayName()));
}
}

foreach (var foreignKey in Metadata.GetForeignKeys())
{
foreignKey.SetPrincipalToDependent((string?)null, configurationSource);
if (foreignKey.PrincipalToDependent == null)
{
continue;
}

if (foreignKey.GetPrincipalToDependentConfigurationSource() == ConfigurationSource.Explicit
&& configurationSource == ConfigurationSource.Explicit)
{
throw new InvalidOperationException(
CoreStrings.NavigationToKeylessType(foreignKey.PrincipalToDependent.Name, Metadata.DisplayName()));
}

foreignKey.Builder.HasNavigation((string?)null, pointsToPrincipal: false, configurationSource);
}

foreach (var key in Metadata.GetKeys().ToList())
Expand Down
23 changes: 21 additions & 2 deletions src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,25 @@ public virtual bool CanSetEntityTypes(
EntityType principalEntityType,
EntityType dependentEntityType,
ConfigurationSource? configurationSource)
=> CanSetEntityTypes(
principalEntityType,
dependentEntityType,
configurationSource,
out _,
out _);

/// <summary>
/// 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
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool CanSetEntityTypes(
EntityType principalEntityType,
EntityType dependentEntityType,
ConfigurationSource? configurationSource,
out bool shouldResetToPrincipal,
out bool shouldResetToDependent)
=> CanSetRelatedTypes(
principalEntityType,
dependentEntityType,
Expand All @@ -1457,8 +1476,8 @@ public virtual bool CanSetEntityTypes(
configurationSource,
shouldThrow: false,
out _,
out _,
out _,
out shouldResetToPrincipal,
out shouldResetToDependent,
out _,
out _,
out _);
Expand Down
12 changes: 6 additions & 6 deletions src/EFCore/Properties/CoreStrings.Designer.cs

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

12 changes: 6 additions & 6 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@
<value>You are configuring a relationship between '{dependentEntityType}' and '{principalEntityType}' but have specified a foreign key on '{entityType}'. The foreign key must be defined on a type that is part of the relationship.</value>
</data>
<data name="DerivedEntityCannotBeKeyless" xml:space="preserve">
<value>Unable to set a base type for entity type '{entityType}' because it has been configured as keyless.</value>
<value>Unable to set a base type for entity type '{entityType}' because it has been configured as keyless. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.</value>
</data>
<data name="DerivedEntityCannotHaveKeys" xml:space="preserve">
<value>Unable to set a base type for entity type '{entityType}' because it has one or more keys defined. Only root types can have keys.</value>
Expand All @@ -415,7 +415,7 @@
<value>Unable to set '{baseEntityType}' as the base type for entity type '{derivedEntityType}' because '{ownedEntityType}' is configured as owned, while '{nonOwnedEntityType}' is non-owned. All entity types in a hierarchy need to have the same ownership status. See https://aka.ms/efcore-docs-owned for more information and examples.</value>
</data>
<data name="DerivedEntityTypeHasNoKey" xml:space="preserve">
<value>'{derivedType}' cannot be configured as keyless because it is a derived type; the root type '{rootType}' must be configured as keyless instead. If you did not intend for '{rootType}' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder in 'OnModelCreating', or referenced from a navigation on a type that is included in the model.</value>
<value>'{derivedType}' cannot be configured as keyless because it is a derived type; the root type '{rootType}' must be configured as keyless instead. If you did not intend for '{rootType}' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder in 'OnModelCreating', or referenced from a navigation on a type that is included in the model. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.</value>
</data>
<data name="DerivedEntityTypeKey" xml:space="preserve">
<value>A key cannot be configured on '{derivedType}' because it is a derived type. The key must be configured on the root type '{rootType}'. If you did not intend for '{rootType}' to be included in the model, ensure that it is not referenced by a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation on a type that is included in the model.</value>
Expand Down Expand Up @@ -481,7 +481,7 @@
<value>Cannot translate '{comparisonOperator}' on a subquery expression of entity type '{entityType}' because it has a composite primary key. See https://go.microsoft.com/fwlink/?linkid=2141942 for information on how to rewrite your query.</value>
</data>
<data name="EntityEqualityOnKeylessEntityNotSupported" xml:space="preserve">
<value>Cannot translate the '{comparisonOperator}' on an expression of entity type '{entityType}' because it is a keyless entity. Consider using entity properties instead.</value>
<value>Cannot translate the '{comparisonOperator}' on an expression of entity type '{entityType}' because it is a keyless entity. Consider using entity properties instead. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.</value>
</data>
<data name="EntityRequiresKey" xml:space="preserve">
<value>The entity type '{entityType}' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.</value>
Expand Down Expand Up @@ -686,7 +686,7 @@
<value>A call was made to '{replaceService}', but Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or build replacement services into the service provider before passing it to '{useInternalServiceProvider}'.</value>
</data>
<data name="InvalidSetKeylessOperation" xml:space="preserve">
<value>The invoked method cannot be used for the entity type '{entityType}' because it does not have a primary key.</value>
<value>The invoked method cannot be used for the entity type '{entityType}' because it does not have a primary key. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.</value>
</data>
<data name="InvalidSetSameTypeWithDifferentNamespace" xml:space="preserve">
<value>Cannot create a DbSet for '{typeName}' because this type is not included in the model for the context. However the model contains an entity type with the same name in a different namespace: '{entityTypeName}'.</value>
Expand Down Expand Up @@ -1154,7 +1154,7 @@
<value>The navigation '{navigation}' cannot be added to the entity type '{entityType}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'.</value>
</data>
<data name="NavigationToKeylessType" xml:space="preserve">
<value>The navigation '{navigation}' cannot be added because it targets the keyless entity type '{entityType}'. Navigations can only target entity types with keys.</value>
<value>The navigation '{navigation}' cannot be added because it targets the keyless entity type '{entityType}'. Navigations can only target entity types with keys. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.</value>
</data>
<data name="NoBackingField" xml:space="preserve">
<value>No backing field was found for property '{1_entityType}.{0_property}'. Name the backing field so that it is discovered by convention, configure the backing field to use, or use a different '{propertyAccessMode}'.</value>
Expand Down Expand Up @@ -1298,7 +1298,7 @@
<value>You are configuring a relationship between '{dependentEntityType}' and '{principalEntityType}', but have specified a principal key on '{entityType}'. The foreign key must target a type that is part of the relationship.</value>
</data>
<data name="PrincipalKeylessType" xml:space="preserve">
<value>The keyless entity type '{entityType}' cannot be on the principal end of the relationship between '{firstNavigationSpecification}' and '{secondNavigationSpecification}'. The principal entity type must have a key.</value>
<value>The keyless entity type '{entityType}' cannot be on the principal end of the relationship between '{firstNavigationSpecification}' and '{secondNavigationSpecification}'. The principal entity type must have a key. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.</value>
</data>
<data name="PrincipalOwnedType" xml:space="preserve">
<value>The relationship from '{referencingEntityTypeOrNavigation}' to '{referencedEntityTypeOrNavigation}' is not supported because the owned entity type '{ownedType}' cannot be on the principal side of a non-ownership relationship. Remove the relationship or configure the foreign key to be on '{ownedType}'.</value>
Expand Down
34 changes: 33 additions & 1 deletion test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2692,7 +2692,7 @@ public virtual void Navigation_to_shared_type_is_not_discovered_by_convention()

Assert.Equal(
CoreStrings.NonConfiguredNavigationToSharedType("Navigation", nameof(CollectionNavigationToSharedType)),
Assert.Throws<InvalidOperationException>(() => modelBuilder.FinalizeModel()).Message);
Assert.Throws<InvalidOperationException>(modelBuilder.FinalizeModel).Message);
}

[ConditionalFact]
Expand Down Expand Up @@ -2723,6 +2723,38 @@ public virtual void WithMany_pointing_to_keyless_entity_throws()
.HasOne(e => e.Reference).WithMany(e => e.Collection)).Message);
}

[ConditionalFact]
public virtual void HasNoKey_call_on_principal_entity_throws()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity<KeylessCollectionNavigation>().HasMany(e => e.Stores).WithOne();
Assert.Equal(
CoreStrings.PrincipalKeylessType(
nameof(KeylessCollectionNavigation),
nameof(KeylessCollectionNavigation) + "." + nameof(KeylessCollectionNavigation.Stores),
nameof(Store)),
Assert.Throws<InvalidOperationException>(
() => modelBuilder.Entity<KeylessCollectionNavigation>().HasNoKey()).Message);
}

[ConditionalFact]
public virtual void HasNoKey_call_on_principal_with_navigation_throws()
{
var modelBuilder = CreateModelBuilder();
modelBuilder.Entity<KeylessReferenceNavigation>();
modelBuilder.Entity<KeylessCollectionNavigation>()
.HasOne(e => e.Reference)
.WithMany(e => e.Collection);

Assert.Equal(
CoreStrings.NavigationToKeylessType(
nameof(KeylessReferenceNavigation.Collection),
nameof(KeylessCollectionNavigation)),
Assert.Throws<InvalidOperationException>(
() => modelBuilder.Entity<KeylessCollectionNavigation>().HasNoKey()).Message);
}

[ConditionalFact]
public virtual void Reference_navigation_from_keyless_entity_type_works()
{
var modelBuilder = CreateModelBuilder();
Expand Down
Loading
Loading