Skip to content

Commit

Permalink
Add Fluent API for skip navigations.
Browse files Browse the repository at this point in the history
Make sure conflicting members are dealt with consistently when setting the base type.
Preserve the service property with the same name on the base type.
Preserve the configuration of conflicting service property with the same name on the derived type.

Part of #19003
  • Loading branch information
AndriySvyryd committed Jan 30, 2020
1 parent 636625e commit 2a1aa1c
Show file tree
Hide file tree
Showing 72 changed files with 4,570 additions and 1,847 deletions.
4 changes: 2 additions & 2 deletions src/EFCore/Extensions/ConventionPropertyBaseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ public static void SetPropertyAccessMode(
propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
/// Returns the configuration source for <see cref="PropertyBaseExtensions.GetPropertyAccessMode" />.
/// Returns the configuration source for <see cref="IPropertyBase.GetPropertyAccessMode" />.
/// </summary>
/// <param name="property"> The property to find configuration source for. </param>
/// <returns> The configuration source for <see cref="PropertyBaseExtensions.GetPropertyAccessMode" />. </returns>
/// <returns> The configuration source for <see cref="IPropertyBase.GetPropertyAccessMode" />. </returns>
public static ConfigurationSource? GetPropertyAccessModeConfigurationSource([NotNull] this IConventionPropertyBase property)
=> property.FindAnnotation(CoreAnnotationNames.PropertyAccessMode)?.GetConfigurationSource();
}
Expand Down
15 changes: 0 additions & 15 deletions src/EFCore/Extensions/PropertyBaseExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,5 @@ public static bool IsShadowProperty([NotNull] this IPropertyBase property)
public static bool IsIndexerProperty([NotNull] this IPropertyBase property)
=> Check.NotNull(property, nameof(property)).GetIdentifyingMemberInfo() is PropertyInfo propertyInfo
&& propertyInfo == property.DeclaringType.FindIndexerPropertyInfo();

/// <summary>
/// <para>
/// Gets the <see cref="PropertyAccessMode" /> being used for this property.
/// <c>null</c> indicates that the default property access mode is being used.
/// </para>
/// </summary>
/// <param name="propertyBase"> The property for which to get the access mode. </param>
/// <returns> The access mode being used, or <c>null</c> if the default access mode is being used. </returns>
public static PropertyAccessMode GetPropertyAccessMode(
[NotNull] this IPropertyBase propertyBase)
=> (PropertyAccessMode)(Check.NotNull(propertyBase, nameof(propertyBase))[CoreAnnotationNames.PropertyAccessMode]
?? (propertyBase is INavigation
? propertyBase.DeclaringType.GetNavigationAccessMode()
: propertyBase.DeclaringType.GetPropertyAccessMode()));
}
}
28 changes: 27 additions & 1 deletion src/EFCore/Infrastructure/ModelValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ var isTargetWeakOrOwned
{
throw new InvalidOperationException(
CoreStrings.AmbiguousOwnedNavigation(
entityType.ClrType.ShortDisplayName(), targetType.ShortDisplayName()));
entityType.DisplayName() + "." + actualProperty.Name, targetType.ShortDisplayName()));
}

throw new InvalidOperationException(
Expand Down Expand Up @@ -318,6 +318,32 @@ protected virtual void ValidateIgnoredMembers(

Check.DebugAssert(false, "Should never get here...");
}

var skipNavigation = entityType.FindSkipNavigation(ignoredMember);
if (skipNavigation != null)
{
if (skipNavigation.DeclaringEntityType != entityType)
{
throw new InvalidOperationException(
CoreStrings.InheritedPropertyCannotBeIgnored(
ignoredMember, entityType.DisplayName(), skipNavigation.DeclaringEntityType.DisplayName()));
}

Check.DebugAssert(false, "Should never get here...");
}

var serviceProperty = entityType.FindServiceProperty(ignoredMember);
if (serviceProperty != null)
{
if (serviceProperty.DeclaringEntityType != entityType)
{
throw new InvalidOperationException(
CoreStrings.InheritedPropertyCannotBeIgnored(
ignoredMember, entityType.DisplayName(), serviceProperty.DeclaringEntityType.DisplayName()));
}

Check.DebugAssert(false, "Should never get here...");
}
}
}
}
Expand Down
164 changes: 164 additions & 0 deletions src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// 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;
using System.ComponentModel;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
/// <summary>
/// <para>
/// Provides a simple API for configuring a one-to-many relationship.
/// </para>
/// <para>
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
/// and it is not designed to be directly constructed in your application code.
/// </para>
/// </summary>
public class CollectionCollectionBuilder
{
/// <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]
public CollectionCollectionBuilder(
[NotNull] IMutableEntityType leftEntityType,
[NotNull] IMutableEntityType rightEntityType,
[NotNull] IMutableSkipNavigation leftNavigation,
[NotNull] IMutableSkipNavigation rightNavigation)
{
Check.NotNull(leftEntityType, nameof(leftEntityType));
Check.NotNull(rightEntityType, nameof(rightEntityType));
Check.NotNull(leftNavigation, nameof(leftNavigation));
Check.NotNull(rightNavigation, nameof(rightNavigation));

Check.DebugAssert(((IConventionEntityType)leftEntityType).Builder != null, "Builder is null");
Check.DebugAssert(((IConventionEntityType)rightEntityType).Builder != null, "Builder is null");
Check.DebugAssert(((IConventionSkipNavigation)leftNavigation).Builder != null, "Builder is null");
Check.DebugAssert(((IConventionSkipNavigation)rightNavigation).Builder != null, "Builder is null");

LeftEntityType = leftEntityType;
RightEntityType = rightEntityType;
LeftNavigation = leftNavigation;
RightNavigation = rightNavigation;
}

/// <summary>
/// One of the entity types involved in the relationship.
/// </summary>
protected virtual IMutableEntityType LeftEntityType { get; }

/// <summary>
/// One of the entity types involved in the relationship.
/// </summary>
protected virtual IMutableEntityType RightEntityType { get; }

/// <summary>
/// One of the navigations involved in the relationship.
/// </summary>
public virtual IMutableSkipNavigation LeftNavigation { get; private set; }

/// <summary>
/// One of the navigations involved in the relationship.
/// </summary>
public virtual IMutableSkipNavigation RightNavigation { get; private set; }

/// <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 InternalModelBuilder ModelBuilder => LeftEntityType.AsEntityType().Model.Builder;

/// <summary>
/// Configures the relationships to the entity types participating in the many-to-many relationship.
/// </summary>
/// <param name="joinEntity"> The type of the join entity. </param>
/// <param name="configureLeft"> The configuration for the relationship to the left entity type. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <returns> The builder for the association type. </returns>
public virtual EntityTypeBuilder UsingEntity(
[NotNull] Type joinEntity,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureRight,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureLeft)
{
var entityTypeBuilder = new EntityTypeBuilder(
ModelBuilder.Entity(joinEntity, ConfigurationSource.Explicit).Metadata);

var leftForeignKey = configureLeft(entityTypeBuilder).Metadata;
var rightForeignKey = configureRight(entityTypeBuilder).Metadata;

Using(rightForeignKey, leftForeignKey);

return entityTypeBuilder;
}

/// <summary>
/// Configures the relationships to the entity types participating in the many-to-many relationship.
/// </summary>
/// <param name="joinEntity"> The type of the join entity. </param>
/// <param name="configureLeft"> The configuration for the relationship to the left entity type. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <param name="configureAssociation"> The configuration of the association type. </param>
/// <returns> The builder for the originating entity type so that multiple configuration calls can be chained. </returns>
public virtual EntityTypeBuilder UsingEntity(
[NotNull] Type joinEntity,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureRight,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureLeft,
[NotNull] Action<EntityTypeBuilder> configureAssociation)
{
var entityTypeBuilder = UsingEntity(joinEntity, configureRight, configureLeft);
configureAssociation(entityTypeBuilder);

return new EntityTypeBuilder(LeftEntityType);
}

/// <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 void Using([NotNull] IMutableForeignKey rightForeignKey, [NotNull] IMutableForeignKey leftForeignKey)
{
var leftBuilder = ((SkipNavigation)LeftNavigation).Builder;
var rightBuilder = ((SkipNavigation)RightNavigation).Builder;

leftBuilder = leftBuilder.HasForeignKey((ForeignKey)leftForeignKey, ConfigurationSource.Explicit);
rightBuilder = rightBuilder.HasForeignKey((ForeignKey)rightForeignKey, ConfigurationSource.Explicit);

leftBuilder = leftBuilder.HasInverse(rightBuilder.Metadata, ConfigurationSource.Explicit);

LeftNavigation = leftBuilder.Metadata;
RightNavigation = leftBuilder.Metadata.Inverse;
}

#region Hidden System.Object members

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
public override string ToString() => base.ToString();

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once BaseObjectEqualsIsObjectEquals
public override bool Equals(object obj) => base.Equals(obj);

/// <inheritdoc />
[EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
public override int GetHashCode() => base.GetHashCode();

#endregion
}
}
84 changes: 84 additions & 0 deletions src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
/// <summary>
/// <para>
/// Provides a simple API for configuring a many-to-many relationship.
/// </para>
/// <para>
/// Instances of this class are returned from methods when using the <see cref="ModelBuilder" /> API
/// and it is not designed to be directly constructed in your application code.
/// </para>
/// </summary>
/// <typeparam name="TLeftEntity"> One of the entity types in this relationship. </typeparam>
/// <typeparam name="TRightEntity"> One of the entity types in this relationship. </typeparam>
public class CollectionCollectionBuilder<TLeftEntity, TRightEntity> : CollectionCollectionBuilder
where TLeftEntity : class
where TRightEntity : class
{
/// <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]
public CollectionCollectionBuilder(
[NotNull] IMutableEntityType leftEntityType,
[NotNull] IMutableEntityType rightEntityType,
[NotNull] IMutableSkipNavigation leftNavigation,
[NotNull] IMutableSkipNavigation rightNavigation)
: base(leftEntityType, rightEntityType, leftNavigation, rightNavigation)
{
}

/// <summary>
/// Configures the relationships to the entity types participating in the many-to-many relationship.
/// </summary>
/// <param name="configureLeft"> The configuration for the relationship to the left entity type. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <typeparam name="TAssociationEntity"> The type of the association entity. </typeparam>
/// <returns> The builder for the association type. </returns>
public virtual EntityTypeBuilder<TAssociationEntity> UsingEntity<TAssociationEntity>(
[NotNull] Func<EntityTypeBuilder<TAssociationEntity>, ReferenceCollectionBuilder<TLeftEntity, TAssociationEntity>> configureRight,
[NotNull] Func<EntityTypeBuilder<TAssociationEntity>, ReferenceCollectionBuilder<TRightEntity, TAssociationEntity>> configureLeft)
where TAssociationEntity : class
{
var entityTypeBuilder = new EntityTypeBuilder<TAssociationEntity>(
ModelBuilder.Entity(typeof(TAssociationEntity), ConfigurationSource.Explicit).Metadata);

var leftForeignKey = configureLeft(entityTypeBuilder).Metadata;
var rightForeignKey = configureRight(entityTypeBuilder).Metadata;

Using(rightForeignKey, leftForeignKey);

return entityTypeBuilder;
}

/// <summary>
/// Configures the relationships to the entity types participating in the many-to-many relationship.
/// </summary>
/// <param name="configureLeft"> The configuration for the relationship to the left entity type. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <param name="configureAssociation"> The configuration of the association type. </param>
/// <typeparam name="TAssociationEntity"> The type of the association entity. </typeparam>
/// <returns> The builder for the originating entity type so that multiple configuration calls can be chained. </returns>
public virtual EntityTypeBuilder<TLeftEntity> UsingEntity<TAssociationEntity>(
[NotNull] Func<EntityTypeBuilder<TAssociationEntity>, ReferenceCollectionBuilder<TLeftEntity, TAssociationEntity>> configureRight,
[NotNull] Func<EntityTypeBuilder<TAssociationEntity>, ReferenceCollectionBuilder<TRightEntity, TAssociationEntity>> configureLeft,
[NotNull] Action<EntityTypeBuilder<TAssociationEntity>> configureAssociation)
where TAssociationEntity : class
{
var entityTypeBuilder = UsingEntity<TAssociationEntity>(configureRight, configureLeft);
configureAssociation(entityTypeBuilder);

return new EntityTypeBuilder<TLeftEntity>(LeftEntityType);
}
}
}
Loading

0 comments on commit 2a1aa1c

Please sign in to comment.