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

Add Fluent API for skip navigations. #19660

Merged
merged 1 commit into from
Jan 30, 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
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