diff --git a/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs b/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs index d9ce6ae7ab5..aada44c618f 100644 --- a/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs +++ b/src/EFCore.Specification.Tests/CustomConvertersTestBase.cs @@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Xunit; +// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore { public abstract class CustomConvertersTestBase : BuiltInDataTypesTestBase diff --git a/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs index e6b67dbd2f4..7f863fe5d62 100644 --- a/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/Internal/RelationshipDiscoveryConvention.cs @@ -96,9 +96,7 @@ private IReadOnlyList FindRelationshipCandidates(Internal var navigationPropertyInfo = candidateTuple.Key; var targetClrType = candidateTuple.Value; - if (entityTypeBuilder.IsIgnored(navigationPropertyInfo.Name, ConfigurationSource.Convention) - || (entityTypeBuilder.Metadata.IsQueryType - && navigationPropertyInfo.PropertyType.TryGetSequenceType() != null)) + if (!IsCandidateNavigationProperty(entityTypeBuilder, navigationPropertyInfo.Name, navigationPropertyInfo)) { continue; } @@ -179,14 +177,15 @@ private IReadOnlyList FindRelationshipCandidates(Internal if (inverseTargetType != entityType.ClrType || navigationPropertyInfo.IsSameAs(inversePropertyInfo) - || candidateTargetEntityTypeBuilder.IsIgnored(inversePropertyInfo.Name, ConfigurationSource.Convention) || entityType.IsQueryType || (ownership != null && (ownership.PrincipalEntityType != candidateTargetEntityType || ownership.PrincipalToDependent.Name != inversePropertyInfo.Name)) || (entityType.HasDefiningNavigation() && (entityType.DefiningEntityType != candidateTargetEntityType - || entityType.DefiningNavigationName != inversePropertyInfo.Name))) + || entityType.DefiningNavigationName != inversePropertyInfo.Name)) + || !IsCandidateNavigationProperty( + candidateTargetEntityTypeBuilder, inversePropertyInfo.Name, inversePropertyInfo)) { continue; } @@ -663,8 +662,10 @@ public virtual bool Apply( string navigationName, MemberInfo propertyInfo) { - sourceEntityTypeBuilder = sourceEntityTypeBuilder.Metadata.Builder; - if (!IsCandidateNavigationProperty(sourceEntityTypeBuilder, navigationName, propertyInfo)) + if ((targetEntityTypeBuilder.Metadata.Builder == null + && sourceEntityTypeBuilder.ModelBuilder.IsIgnored( + targetEntityTypeBuilder.Metadata.Name, ConfigurationSource.Convention)) + || !IsCandidateNavigationProperty(sourceEntityTypeBuilder, navigationName, propertyInfo)) { return true; } @@ -696,35 +697,13 @@ private bool Apply(EntityType entityType, MemberInfo navigationProperty) [ContractAnnotation("propertyInfo:null => false")] private static bool IsCandidateNavigationProperty( InternalEntityTypeBuilder sourceEntityTypeBuilder, string navigationName, MemberInfo propertyInfo) - { - if (propertyInfo == null) - { - return false; - } - - if (sourceEntityTypeBuilder == null - || sourceEntityTypeBuilder.ModelBuilder.IsIgnored(sourceEntityTypeBuilder.Metadata.Name, ConfigurationSource.Convention)) - { - return false; - } - - if (sourceEntityTypeBuilder.IsIgnored(navigationName, ConfigurationSource.Convention)) - { - return false; - } - - if (sourceEntityTypeBuilder.Metadata.FindProperty(navigationName) != null) - { - return false; - } - - if (sourceEntityTypeBuilder.Metadata.FindServiceProperty(navigationName) != null) - { - return false; - } - - return true; - } + => propertyInfo != null + && sourceEntityTypeBuilder != null + && !sourceEntityTypeBuilder.IsIgnored(navigationName, ConfigurationSource.Convention) + && sourceEntityTypeBuilder.Metadata.FindProperty(navigationName) == null + && sourceEntityTypeBuilder.Metadata.FindServiceProperty(navigationName) == null + && (!sourceEntityTypeBuilder.Metadata.IsQueryType + || (propertyInfo as PropertyInfo)?.PropertyType.TryGetSequenceType() == null); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 4b424a63191..71294e02198 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -398,15 +398,19 @@ private InternalPropertyBuilder Property( if ((duplicateNavigation.IsDependentToPrincipal() ? foreignKey.GetDependentToPrincipalConfigurationSource() : foreignKey.GetPrincipalToDependentConfigurationSource()) - .Overrides(configurationSource) - && configurationSource == ConfigurationSource.Explicit) + .Overrides(configurationSource)) { - throw new InvalidOperationException(CoreStrings.PropertyCalledOnNavigation(propertyName, Metadata.DisplayName())); + if (configurationSource == ConfigurationSource.Explicit) + { + throw new InvalidOperationException(CoreStrings.PropertyCalledOnNavigation(propertyName, Metadata.DisplayName())); + } + + return null; } if (foreignKey.GetConfigurationSource() == ConfigurationSource.Convention) { - RemoveForeignKey(foreignKey, ConfigurationSource.Convention); + foreignKey.DeclaringEntityType.Builder.RemoveForeignKey(foreignKey, ConfigurationSource.Convention); } else { diff --git a/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs b/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs index 607499dcb24..7831e242c68 100644 --- a/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs @@ -1444,7 +1444,8 @@ public virtual InternalRelationshipBuilder HasForeignKey( return builder; } - if (!CanSetForeignKey(properties, dependentEntityType, configurationSource, out var resetIsRequired, out var resetPrincipalKey)) + if (!CanSetForeignKey( + properties, dependentEntityType, configurationSource, out var resetIsRequired, out var resetPrincipalKey)) { return null; } diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index ffe0c8d1bd4..649a28241a0 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -419,6 +419,22 @@ public virtual void Can_add_shadow_properties_when_they_have_been_ignored() Assert.NotNull(modelBuilder.Model.FindEntityType(typeof(Customer)).FindProperty("Shadow")); } + [Fact] + public virtual void Can_override_navigations_as_properties() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + modelBuilder.Entity(); + + var customer = model.FindEntityType(typeof(Customer)); + Assert.NotNull(customer.FindNavigation(nameof(Customer.Orders))); + + modelBuilder.Entity().Property(c => c.Orders); + + Assert.Null(customer.FindNavigation(nameof(Customer.Orders))); + Assert.NotNull(customer.FindProperty(nameof(Customer.Orders))); + } + [Fact] public virtual void Ignoring_a_navigation_property_removes_discovered_entity_types() {