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

Re-run fk discovery convention when skip navigation changes #22153

Merged
1 commit merged into from
Aug 24, 2020
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
5 changes: 5 additions & 0 deletions src/EFCore/Extensions/PropertyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,11 @@ public static string ToDebugString(
builder.Append(" Shadow");
}

if (property.IsIndexerProperty())
{
builder.Append(" Indexer");
}

if (!property.IsNullable)
{
builder.Append(" Required");
Expand Down
18 changes: 11 additions & 7 deletions src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ protected virtual void ValidatePropertyMapping(
var unmappedProperty = entityType.GetDeclaredProperties().FirstOrDefault(
p => (!ConfigurationSource.Convention.Overrides(p.GetConfigurationSource())
// Use a better condition of non-persisted properties when issue#14121 is implemented
|| !(p.IsShadowProperty() || (p.DeclaringEntityType.IsPropertyBag && p.IsIndexerProperty())))
|| !p.IsImplicitlyCreated())
&& p.FindTypeMapping() == null);

if (unmappedProperty != null)
Expand Down Expand Up @@ -398,13 +398,12 @@ protected virtual void ValidateNoShadowKeys(
{
Check.NotNull(model, nameof(model));

foreach (var entityType in model.GetEntityTypes().Where(t => t.ClrType != null))
foreach (IConventionEntityType entityType in model.GetEntityTypes().Where(t => t.ClrType != null))
{
foreach (var key in entityType.GetDeclaredKeys())
{
if (key.Properties.Any(p => p.IsShadowProperty())
&& key is Key concreteKey
&& ConfigurationSource.Convention.Overrides(concreteKey.GetConfigurationSource())
if (key.Properties.Any(p => p.IsImplicitlyCreated())
&& ConfigurationSource.Convention.Overrides(key.GetConfigurationSource())
&& !key.IsPrimaryKey())
{
var referencingFk = key.GetReferencingForeignKeys().FirstOrDefault();
Expand Down Expand Up @@ -1162,11 +1161,16 @@ protected virtual void LogShadowProperties(
{
Check.NotNull(model, nameof(model));

foreach (var entityType in model.GetEntityTypes().Where(t => t.ClrType != null))
foreach (IConventionEntityType entityType in model.GetEntityTypes())
{
if (entityType.ClrType == null)
{
continue;
}

foreach (var property in entityType.GetDeclaredProperties())
{
if (property.IsShadowProperty())
if (property.IsImplicitlyCreated())
{
logger.ShadowPropertyCreated(property);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
Expand Down Expand Up @@ -48,6 +48,8 @@ public class ForeignKeyPropertyDiscoveryConvention :
IForeignKeyPrincipalEndChangedConvention,
IForeignKeyUniquenessChangedConvention,
IForeignKeyRequirednessChangedConvention,
ISkipNavigationForeignKeyChangedConvention,
ISkipNavigationInverseChangedConvention,
IKeyAddedConvention,
IKeyRemovedConvention,
IEntityTypePrimaryKeyChangedConvention,
Expand Down Expand Up @@ -135,7 +137,7 @@ private IConventionForeignKeyBuilder DiscoverProperties(
foreach (var fkProperty in foreignKey.Properties)
{
if (ConfigurationSource.Convention.Overrides(fkProperty.GetTypeConfigurationSource())
&& fkProperty.IsShadowProperty()
&& (fkProperty.IsShadowProperty() || fkProperty.IsIndexerProperty())
&& fkProperty.ClrType.IsNullableType() == foreignKey.IsRequired
&& fkProperty.GetContainingForeignKeys().All(otherFk => otherFk.IsRequired == foreignKey.IsRequired))
{
Expand Down Expand Up @@ -279,7 +281,7 @@ private IConventionForeignKeyBuilder DiscoverProperties(

if (foreignKeyProperties == null)
{
return ((ForeignKey)foreignKey).Builder.ReuniquifyTemporaryProperties(false);
return ((ForeignKey)foreignKey).Builder.ReuniquifyImplicitProperties(false);
}

var conflictingFKCount = foreignKey.DeclaringEntityType.FindForeignKeys(foreignKeyProperties)
Expand All @@ -290,13 +292,13 @@ private IConventionForeignKeyBuilder DiscoverProperties(
if (foreignKey.Properties.SequenceEqual(foreignKeyProperties))
{
return conflictingFKCount > 1
? ((ForeignKey)foreignKey).Builder.ReuniquifyTemporaryProperties(true)
? ((ForeignKey)foreignKey).Builder.ReuniquifyImplicitProperties(true)
: relationshipBuilder;
}

if (conflictingFKCount > 0)
{
return ((ForeignKey)foreignKey).Builder.ReuniquifyTemporaryProperties(false);
return ((ForeignKey)foreignKey).Builder.ReuniquifyImplicitProperties(false);
}

var newRelationshipBuilder = relationshipBuilder.HasForeignKey(foreignKeyProperties);
Expand Down Expand Up @@ -422,7 +424,7 @@ private bool TryFindMatchingProperties(
shouldThrow: false))
{
if (propertiesToReference.All(
p => !p.IsShadowProperty()
p => !p.IsImplicitlyCreated()
|| p.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation)))
{
var dependentNavigationSpec = onDependent
Expand Down Expand Up @@ -482,7 +484,7 @@ private static IConventionProperty TryGetProperty(IConventionEntityType entityTy
{
foreach (var property in entityType.GetProperties())
{
if ((!(property.IsShadowProperty() || entityType.IsPropertyBag && property.IsIndexerProperty())
if ((!property.IsImplicitlyCreated()
|| !ConfigurationSource.Convention.Overrides(property.GetConfigurationSource()))
&& property.Name.Length == prefix.Length + suffix.Length
&& property.Name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
Expand Down Expand Up @@ -524,7 +526,7 @@ public virtual void ProcessPropertyAdded(
private void Process(IConventionPropertyBuilder propertyBuilder, IConventionContext context)
{
var property = propertyBuilder.Metadata;
if (property.IsShadowProperty()
if (property.IsImplicitlyCreated()
&& ConfigurationSource.Convention.Overrides(property.GetConfigurationSource()))
{
return;
Expand Down Expand Up @@ -756,6 +758,37 @@ public virtual void ProcessEntityTypePrimaryKeyChanged(
}
}

/// <inheritdoc />
public virtual void ProcessSkipNavigationForeignKeyChanged(
IConventionSkipNavigationBuilder skipNavigationBuilder,
IConventionForeignKey foreignKey,
IConventionForeignKey oldForeignKey,
IConventionContext<IConventionForeignKey> context)
{
if (foreignKey != null
&& foreignKey.GetPropertiesConfigurationSource() == null
&& skipNavigationBuilder.Metadata.Inverse?.Builder != null)
{
DiscoverProperties(foreignKey.Builder, context);
}
}

/// <inheritdoc />
public virtual void ProcessSkipNavigationInverseChanged(
IConventionSkipNavigationBuilder skipNavigationBuilder,
IConventionSkipNavigation inverse,
IConventionSkipNavigation oldInverse,
IConventionContext<IConventionSkipNavigation> context)
{
var foreignKey = skipNavigationBuilder.Metadata.ForeignKey;
if (foreignKey != null
&& foreignKey.GetPropertiesConfigurationSource() == null
&& inverse?.Builder != null)
{
DiscoverProperties(foreignKey.Builder, context);
}
}

/// <inheritdoc />
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
Expand Down Expand Up @@ -802,33 +835,44 @@ public virtual void ProcessModelFinalizing(

if (HasUniquifiedProperties(foreignKey))
{
var conflictingShadowFk = entityType.GetDeclaredForeignKeys().FirstOrDefault(
var conflictingFk = entityType.GetDeclaredForeignKeys().FirstOrDefault(
otherForeignKey =>
otherForeignKey != foreignKey
&& otherForeignKey.PrincipalEntityType == foreignKey.PrincipalEntityType
&& otherForeignKey.GetPropertiesConfigurationSource() == null);
if (conflictingShadowFk != null)
if (conflictingFk != null)
{
conflictingFkFound = true;
Dependencies.Logger.ConflictingShadowForeignKeysWarning(conflictingShadowFk);
Dependencies.Logger.ConflictingShadowForeignKeysWarning(conflictingFk);
}
}
}
}
}

private static string GetPropertyBaseName(IConventionForeignKey foreignKey)
/// <summary>
/// Gets the string that should be used as part of the shadow properties created for the given foreign key.
/// </summary>
/// <param name="foreignKey"> The foreign key. </param>
/// <returns> The string that should be used as part of the shadow properties created for the given foreign key. </returns>
public static string GetPropertyBaseName([NotNull] IForeignKey foreignKey)
=> foreignKey.DependentToPrincipal?.Name
?? foreignKey.GetReferencingSkipNavigations().FirstOrDefault()?.Inverse?.Name
?? foreignKey.PrincipalEntityType.ShortName();

private static bool HasUniquifiedProperties(IConventionForeignKey foreignKey)
{
if (foreignKey.GetPropertiesConfigurationSource() != null)
{
return false;
}

var fkBaseName = GetPropertyBaseName(foreignKey);
for (var i = 0; i < foreignKey.Properties.Count; i++)
{
var property = foreignKey.Properties[i];
if (!ConfigurationSource.Convention.Overrides(property.GetConfigurationSource()))
if (!ConfigurationSource.Convention.Overrides(property.GetConfigurationSource())
|| !property.IsImplicitlyCreated())
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,11 @@ public virtual ConventionSet CreateConventionSet()
conventionSet.SkipNavigationRemovedConventions.Add(manyToManyJoinEntityTypeConvention);

conventionSet.SkipNavigationInverseChangedConventions.Add(manyToManyJoinEntityTypeConvention);
conventionSet.SkipNavigationInverseChangedConventions.Add(foreignKeyPropertyDiscoveryConvention);

conventionSet.SkipNavigationForeignKeyChangedConventions.Add(manyToManyJoinEntityTypeConvention);
conventionSet.SkipNavigationForeignKeyChangedConventions.Add(keyDiscoveryConvention);
conventionSet.SkipNavigationForeignKeyChangedConventions.Add(foreignKeyPropertyDiscoveryConvention);

conventionSet.NavigationRemovedConventions.Add(relationshipDiscoveryConvention);

Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ protected virtual void TryConfigurePrimaryKey([NotNull] IConventionEntityTypeBui
if (keyProperties == null)
{
var candidateProperties = entityType.GetProperties().Where(
p => !p.IsShadowProperty()
p => !p.IsImplicitlyCreated()
|| !ConfigurationSource.Convention.Overrides(p.GetConfigurationSource()));
keyProperties = DiscoverKeyProperties(entityType, candidateProperties).ToList();
if (keyProperties.Count > 1)
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Metadata/IConventionProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,13 @@ public interface IConventionProperty : IProperty, IConventionPropertyBase
/// </summary>
/// <returns> The configuration source for <see cref="IProperty.IsConcurrencyToken" />. </returns>
ConfigurationSource? GetIsConcurrencyTokenConfigurationSource();

/// <summary>
/// Returns a value indicating whether the property was created implicitly and isn't based on the CLR model.
/// </summary>
/// <returns> A value indicating whether the property was created implicitly and isn't based on the CLR model. </returns>
bool IsImplicitlyCreated()
=> (this.IsShadowProperty() || (DeclaringEntityType.IsPropertyBag && this.IsIndexerProperty()))
&& GetConfigurationSource() == ConfigurationSource.Convention;
}
}
31 changes: 22 additions & 9 deletions src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.EntityFrameworkCore.ValueGeneration.Internal;
Expand Down Expand Up @@ -445,8 +446,8 @@ public virtual InternalPropertyBuilder Property(
ConfigurationSource? configurationSource)
=> Property(
propertyType, propertyName, memberInfo: null,
typeConfigurationSource: typeConfigurationSource,
configurationSource: configurationSource);
typeConfigurationSource,
configurationSource);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -527,7 +528,18 @@ private InternalPropertyBuilder Property(
return existingProperty.Builder;
}

if (!configurationSource.Overrides(existingProperty.GetConfigurationSource()))
if (memberInfo == null
|| (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty()))
{
if (existingProperty.GetTypeConfigurationSource() is ConfigurationSource existingTypeConfigurationSource
&& !typeConfigurationSource.Overrides(existingTypeConfigurationSource))
{
return null;
}

memberInfo ??= existingProperty.GetIdentifyingMemberInfo();
}
else if (!configurationSource.Overrides(existingProperty.GetConfigurationSource()))
{
return null;
}
Expand Down Expand Up @@ -2173,8 +2185,7 @@ public virtual InternalEntityTypeBuilder RemoveUnusedImplicitProperties<T>([NotN
foreach (var property in properties)
{
if (property?.Builder != null
&& (property.IsShadowProperty()
|| property.DeclaringEntityType.IsPropertyBag))
&& property.IsImplicitlyCreated())
{
RemovePropertyIfUnused((Property)(object)property, ConfigurationSource.Convention);
}
Expand Down Expand Up @@ -2827,7 +2838,7 @@ private InternalForeignKeyBuilder HasRelationship(
dependentProperties: null,
principalKey: null,
propertyBaseName: navigationProperty?.GetSimpleMemberName(),
required: required,
required,
configurationSource);
}
else
Expand Down Expand Up @@ -3572,6 +3583,8 @@ private ForeignKey SetOrAddForeignKey(
{
oldKey.DeclaringEntityType.Builder.RemoveKeyIfUnused(oldKey);
}

propertyBaseName ??= ForeignKeyPropertyDiscoveryConvention.GetPropertyBaseName(foreignKey);
}

var baseName = string.IsNullOrEmpty(propertyBaseName)
Expand Down Expand Up @@ -3968,7 +3981,7 @@ private IReadOnlyList<Property> CreateUniqueProperties(
var currentProperty = currentProperties?.SingleOrDefault(p => p.Name == propertyName);
if (currentProperty != null)
{
if (currentProperty.IsShadowProperty()
if (((IConventionProperty)currentProperty).IsImplicitlyCreated()
&& currentProperty.ClrType != clrType
&& isRequired)
{
Expand Down Expand Up @@ -4044,7 +4057,7 @@ public virtual IReadOnlyList<Property> GetOrCreateProperties(
else if (configurationSource.HasValue)
{
if (ConfigurationSource.Convention.Overrides(property.GetTypeConfigurationSource())
&& property.IsShadowProperty()
&& (property.IsShadowProperty() || property.IsIndexerProperty())
&& (!property.IsNullable || (required && property.GetIsNullableConfigurationSource() == null))
&& property.ClrType.IsNullableType())
{
Expand Down Expand Up @@ -4376,7 +4389,7 @@ private bool CanAddDiscriminatorProperty(
{
var conflictingProperty = Metadata.FindPropertiesInHierarchy(name).FirstOrDefault();
if (conflictingProperty != null
&& conflictingProperty.IsShadowProperty()
&& (conflictingProperty.IsShadowProperty() || conflictingProperty.IsIndexerProperty())
&& conflictingProperty.ClrType != propertyType
&& typeConfigurationSource != null
&& !typeConfigurationSource.Overrides(conflictingProperty.GetTypeConfigurationSource()))
Expand Down
Loading