Skip to content

Commit

Permalink
Partial Fix for 6674. Allow inline configuration of Navigations as th…
Browse files Browse the repository at this point in the history
…ey are created (#20195)

Partial Fix for 6674.
Updated methods for all HasOne-WithMany, HasMany-WithOne and HasOne-WithOne. Generic and non-generic.
  • Loading branch information
lajones committed Mar 8, 2020
1 parent f1a6840 commit e318524
Show file tree
Hide file tree
Showing 25 changed files with 1,133 additions and 159 deletions.
40 changes: 31 additions & 9 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,19 @@ public CollectionNavigationBuilder(
/// The name of the reference navigation property on the other end of this relationship.
/// If null or not specified, then there is no navigation property on the other end of the relationship.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object to further configure the relationship. </returns>
public virtual ReferenceCollectionBuilder WithOne([CanBeNull] string navigationName = null)
public virtual ReferenceCollectionBuilder WithOne(
[CanBeNull] string navigationName = null,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
=> new ReferenceCollectionBuilder(
DeclaringEntityType,
RelatedEntityType,
WithOneBuilder(Check.NullButNotEmpty(navigationName, nameof(navigationName))).Metadata);
WithOneBuilder(
Check.NullButNotEmpty(navigationName, nameof(navigationName)),
navigationConfiguration).Metadata);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -135,8 +142,10 @@ public virtual ReferenceCollectionBuilder WithOne([CanBeNull] string navigationN
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
protected virtual InternalRelationshipBuilder WithOneBuilder([CanBeNull] string navigationName)
=> WithOneBuilder(MemberIdentity.Create(navigationName));
protected virtual InternalRelationshipBuilder WithOneBuilder(
[CanBeNull] string navigationName,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
=> WithOneBuilder(MemberIdentity.Create(navigationName), navigationConfiguration);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand All @@ -145,10 +154,14 @@ protected virtual InternalRelationshipBuilder WithOneBuilder([CanBeNull] string
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
protected virtual InternalRelationshipBuilder WithOneBuilder([CanBeNull] MemberInfo navigationMemberInfo)
=> WithOneBuilder(MemberIdentity.Create(navigationMemberInfo));

private InternalRelationshipBuilder WithOneBuilder(MemberIdentity reference)
protected virtual InternalRelationshipBuilder WithOneBuilder(
[CanBeNull] MemberInfo navigationMemberInfo,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
=> WithOneBuilder(MemberIdentity.Create(navigationMemberInfo), navigationConfiguration);

private InternalRelationshipBuilder WithOneBuilder(
MemberIdentity reference,
Action<NavigationBuilder> navigationConfiguration = null)
{
if (SkipNavigation != null)
{
Expand All @@ -174,7 +187,7 @@ private InternalRelationshipBuilder WithOneBuilder(MemberIdentity reference)
InternalRelationshipBuilder.ThrowForConflictingNavigation(foreignKey, referenceName, newToPrincipal: true);
}

return reference.MemberInfo == null || CollectionMember == null
var withOneBuilder = reference.MemberInfo == null || CollectionMember == null
? Builder.HasNavigations(
reference.Name, CollectionName,
(EntityType)DeclaringEntityType, (EntityType)RelatedEntityType,
Expand All @@ -183,6 +196,15 @@ private InternalRelationshipBuilder WithOneBuilder(MemberIdentity reference)
reference.MemberInfo, CollectionMember,
(EntityType)DeclaringEntityType, (EntityType)RelatedEntityType,
ConfigurationSource.Explicit);

if (navigationConfiguration != null
&& withOneBuilder.Metadata.DependentToPrincipal != null)
{
navigationConfiguration(
new NavigationBuilder(withOneBuilder.Metadata.DependentToPrincipal));
}

return withOneBuilder;
}

/// <summary>
Expand Down
25 changes: 20 additions & 5 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,21 @@ public CollectionNavigationBuilder(
/// The name of the reference navigation property on the other end of this relationship.
/// If null, there is no navigation property on the other end of the relationship.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object to further configure the relationship. </returns>
public new virtual ReferenceCollectionBuilder<TEntity, TRelatedEntity> WithOne([CanBeNull] string navigationName = null)
=> new ReferenceCollectionBuilder<TEntity, TRelatedEntity>(
public new virtual ReferenceCollectionBuilder<TEntity, TRelatedEntity> WithOne(
[CanBeNull] string navigationName = null,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
return new ReferenceCollectionBuilder<TEntity, TRelatedEntity>(
DeclaringEntityType,
RelatedEntityType,
WithOneBuilder(Check.NullButNotEmpty(navigationName, nameof(navigationName))).Metadata);
WithOneBuilder(
Check.NullButNotEmpty(navigationName, nameof(navigationName)),
navigationConfiguration).Metadata);
}

/// <summary>
/// <para>
Expand All @@ -72,13 +81,19 @@ public CollectionNavigationBuilder(
/// relationship (<c>post => post.Blog</c>). If no property is specified, the relationship will be
/// configured without a navigation property on the other end of the relationship.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object to further configure the relationship. </returns>
public virtual ReferenceCollectionBuilder<TEntity, TRelatedEntity> WithOne(
[CanBeNull] Expression<Func<TRelatedEntity, TEntity>> navigationExpression)
[CanBeNull] Expression<Func<TRelatedEntity, TEntity>> navigationExpression,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
=> new ReferenceCollectionBuilder<TEntity, TRelatedEntity>(
DeclaringEntityType,
RelatedEntityType,
WithOneBuilder(navigationExpression?.GetPropertyAccess()).Metadata);
WithOneBuilder(
navigationExpression?.GetPropertyAccess(),
navigationConfiguration).Metadata);

/// <summary>
/// Configures this as a many-to-many relationship.
Expand Down
123 changes: 102 additions & 21 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -575,23 +575,27 @@ private OwnedNavigationBuilder OwnsManyBuilder(in TypeIdentity ownedType, string
/// no property is specified, the relationship will be configured without a navigation property on this
/// end.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object that can be used to configure the relationship. </returns>
public virtual ReferenceNavigationBuilder HasOne(
[NotNull] string relatedTypeName,
[CanBeNull] string navigationName)
[CanBeNull] string navigationName,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
Check.NotEmpty(relatedTypeName, nameof(relatedTypeName));
Check.NullButNotEmpty(navigationName, nameof(navigationName));

var relatedEntityType = FindRelatedEntityType(relatedTypeName, navigationName);
var foreignKey = HasOneBuilder(
MemberIdentity.Create(navigationName), relatedEntityType, navigationConfiguration);

return new ReferenceNavigationBuilder(
Builder.Metadata,
relatedEntityType,
navigationName,
Builder.HasRelationship(
relatedEntityType, navigationName, ConfigurationSource.Explicit,
targetIsPrincipal: Builder.Metadata == relatedEntityType ? true : (bool?)null).Metadata);
foreignKey);
}

/// <summary>
Expand All @@ -618,23 +622,27 @@ public virtual ReferenceNavigationBuilder HasOne(
/// no property is specified, the relationship will be configured without a navigation property on this
/// end.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object that can be used to configure the relationship. </returns>
public virtual ReferenceNavigationBuilder HasOne(
[NotNull] Type relatedType,
[CanBeNull] string navigationName = null)
[CanBeNull] string navigationName = null,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
Check.NotNull(relatedType, nameof(relatedType));
Check.NullButNotEmpty(navigationName, nameof(navigationName));

var relatedEntityType = FindRelatedEntityType(relatedType, navigationName);
var foreignKey = HasOneBuilder(
MemberIdentity.Create(navigationName), relatedEntityType, navigationConfiguration);

return new ReferenceNavigationBuilder(
Builder.Metadata,
relatedEntityType,
navigationName,
Builder.HasRelationship(
relatedEntityType, navigationName, ConfigurationSource.Explicit,
targetIsPrincipal: Builder.Metadata == relatedEntityType ? true : (bool?)null).Metadata);
foreignKey);
}

/// <summary>
Expand All @@ -654,15 +662,61 @@ public virtual ReferenceNavigationBuilder HasOne(
/// The name of the reference navigation property on this entity type that represents
/// the relationship. The navigation must be a CLR property on the entity type.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object that can be used to configure the relationship. </returns>
public virtual ReferenceNavigationBuilder HasOne(
[NotNull] string navigationName)
[NotNull] string navigationName,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
Check.NotEmpty(navigationName, nameof(navigationName));

return Metadata.ClrType == null
? HasOne(navigationName, null) // Path only used by pre 3.0 snapshots
: HasOne(Metadata.GetNavigationMemberInfo(navigationName).GetMemberType(), navigationName);
? HasOne(navigationName, null, navigationConfiguration) // Path only used by pre 3.0 snapshots
: HasOne(Metadata.GetNavigationMemberInfo(navigationName).GetMemberType(), navigationName, navigationConfiguration);
}

/// <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>
[EntityFrameworkInternal]
protected virtual ForeignKey HasOneBuilder(
MemberIdentity navigationId,
[NotNull] EntityType relatedEntityType,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
ForeignKey foreignKey;
if (navigationId.MemberInfo != null)
{
foreignKey = Builder.HasRelationship(
relatedEntityType, navigationId.MemberInfo, ConfigurationSource.Explicit,
targetIsPrincipal: Builder.Metadata == relatedEntityType ? true : (bool?)null).Metadata;
}
else
{
foreignKey = Builder.HasRelationship(
relatedEntityType, navigationId.Name, ConfigurationSource.Explicit,
targetIsPrincipal: Builder.Metadata == relatedEntityType ? true : (bool?)null).Metadata;
}

if (navigationConfiguration != null
&& navigationId.Name != null)
{
var navigation =
Builder.Metadata == relatedEntityType
|| foreignKey.PrincipalEntityType == relatedEntityType
? foreignKey.DependentToPrincipal
: foreignKey.PrincipalToDependent;

navigationConfiguration(
new NavigationBuilder(navigation));
}

return foreignKey;
}

/// <summary>
Expand All @@ -683,15 +737,21 @@ public virtual ReferenceNavigationBuilder HasOne(
/// no property is specified, the relationship will be configured without a navigation property on this
/// end.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object that can be used to configure the relationship. </returns>
public virtual CollectionNavigationBuilder HasMany(
[NotNull] string relatedTypeName,
[CanBeNull] string navigationName)
[CanBeNull] string navigationName,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
Check.NotEmpty(relatedTypeName, nameof(relatedTypeName));
Check.NullButNotEmpty(navigationName, nameof(navigationName));

return HasMany(navigationName, FindRelatedEntityType(relatedTypeName, navigationName));
return HasMany(navigationName,
FindRelatedEntityType(relatedTypeName, navigationName),
navigationConfiguration);
}

/// <summary>
Expand All @@ -710,15 +770,19 @@ public virtual CollectionNavigationBuilder HasMany(
/// The name of the collection navigation property on this entity type that represents the relationship.
/// The navigation must be a CLR property on the entity type.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object that can be used to configure the relationship. </returns>
public virtual CollectionNavigationBuilder HasMany(
[NotNull] string navigationName)
[NotNull] string navigationName,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
Check.NotEmpty(navigationName, nameof(navigationName));

if (Metadata.ClrType == null)
{
return HasMany(navigationName, (string)null);
return HasMany(navigationName, (string)null, navigationConfiguration);
}

var memberType = Metadata.GetNavigationMemberInfo(navigationName).GetMemberType();
Expand All @@ -734,7 +798,7 @@ public virtual CollectionNavigationBuilder HasMany(
"T"));
}

return HasMany(elementType, navigationName);
return HasMany(elementType, navigationName, navigationConfiguration);
}

/// <summary>
Expand All @@ -760,18 +824,27 @@ public virtual CollectionNavigationBuilder HasMany(
/// no property is specified, the relationship will be configured without a navigation property on this
/// end.
/// </param>
/// <param name="navigationConfiguration">
/// An optional action which further configures the navigation property.
/// </param>
/// <returns> An object that can be used to configure the relationship. </returns>
public virtual CollectionNavigationBuilder HasMany(
[NotNull] Type relatedType,
[CanBeNull] string navigationName = null)
[CanBeNull] string navigationName = null,
[CanBeNull] Action<NavigationBuilder> navigationConfiguration = null)
{
Check.NotNull(relatedType, nameof(relatedType));
Check.NullButNotEmpty(navigationName, nameof(navigationName));;
Check.NullButNotEmpty(navigationName, nameof(navigationName));

return HasMany(navigationName, FindRelatedEntityType(relatedType, navigationName));
return HasMany(navigationName,
FindRelatedEntityType(relatedType, navigationName),
navigationConfiguration);
}

private CollectionNavigationBuilder HasMany(string navigationName, EntityType relatedEntityType)
private CollectionNavigationBuilder HasMany(
string navigationName,
EntityType relatedEntityType,
Action<NavigationBuilder> navigationConfiguration = null)
{
var skipNavigation = navigationName != null ? Builder.Metadata.FindSkipNavigation(navigationName) : null;

Expand All @@ -783,11 +856,19 @@ private CollectionNavigationBuilder HasMany(string navigationName, EntityType re
.IsUnique(false, ConfigurationSource.Explicit);
}

var foreignKey = relationship?.Metadata;
if (navigationConfiguration != null
&& foreignKey?.PrincipalToDependent != null)
{
navigationConfiguration(
new NavigationBuilder(foreignKey.PrincipalToDependent));
}

return new CollectionNavigationBuilder(
Builder.Metadata,
relatedEntityType,
new MemberIdentity(navigationName),
relationship?.Metadata,
foreignKey,
skipNavigation);
}

Expand Down
Loading

0 comments on commit e318524

Please sign in to comment.