Skip to content

Commit

Permalink
Create Join Entity automatically by RelationshipDiscoveryConvention a…
Browse files Browse the repository at this point in the history
…t convention-time and through HasMany().WithMany() for fluent API.
  • Loading branch information
lajones committed Jul 8, 2020
1 parent b22dda9 commit 61f6cb9
Show file tree
Hide file tree
Showing 34 changed files with 1,397 additions and 93 deletions.
18 changes: 18 additions & 0 deletions src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.ComponentModel;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -94,6 +95,23 @@ public virtual EntityTypeBuilder UsingEntity(
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureRight,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureLeft)
{
if (((Model)LeftEntityType.Model).IsShared(joinEntity))
{
//TODO #9914 - when the generic version of "please use the shared-type entity type version of this API"
// is available then update to use that.
throw new InvalidOperationException(
CoreStrings.DoNotUseUsingEntityOnSharedClrType(joinEntity.GetType().Name));
}

var existingAssociationEntityType = (EntityType)
(LeftNavigation.ForeignKey?.DeclaringEntityType
?? RightNavigation.ForeignKey?.DeclaringEntityType);
if (existingAssociationEntityType != null)
{
ModelBuilder.RemoveAssociationEntityIfCreatedImplicitly(
existingAssociationEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit);
}

var entityTypeBuilder = new EntityTypeBuilder(
ModelBuilder.Entity(joinEntity, ConfigurationSource.Explicit).Metadata);

Expand Down
19 changes: 19 additions & 0 deletions src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
Expand Down Expand Up @@ -50,6 +52,23 @@ public virtual EntityTypeBuilder<TAssociationEntity> UsingEntity<TAssociationEnt
[NotNull] Func<EntityTypeBuilder<TAssociationEntity>, ReferenceCollectionBuilder<TRightEntity, TAssociationEntity>> configureLeft)
where TAssociationEntity : class
{
if (((Model)LeftEntityType.Model).IsShared(typeof(TAssociationEntity)))
{
//TODO #9914 - when the generic version of "please use the shared-type entity type version of this API"
// is available then update to use that.
throw new InvalidOperationException(
CoreStrings.DoNotUseUsingEntityOnSharedClrType(typeof(TAssociationEntity).Name));
}

var existingAssociationEntityType = (EntityType)
(LeftNavigation.ForeignKey?.DeclaringEntityType
?? RightNavigation.ForeignKey?.DeclaringEntityType);
if (existingAssociationEntityType != null)
{
ModelBuilder.RemoveAssociationEntityIfCreatedImplicitly(
existingAssociationEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit);
}

var entityTypeBuilder = new EntityTypeBuilder<TAssociationEntity>(
ModelBuilder.Entity(typeof(TAssociationEntity), ConfigurationSource.Explicit).Metadata);

Expand Down
80 changes: 65 additions & 15 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,38 @@ private InternalForeignKeyBuilder WithOneBuilder(MemberIdentity reference)
{
if (SkipNavigation != null)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipNavigation(
SkipNavigation.DeclaringEntityType.DisplayName() + "." + SkipNavigation.Name,
RelatedEntityType.DisplayName() + (reference.Name == null
? ""
: "." + reference.Name),
SkipNavigation.DeclaringEntityType.DisplayName() + "." + SkipNavigation.Name,
SkipNavigation.TargetEntityType.DisplayName() + (SkipNavigation.Inverse == null
? ""
: "." + SkipNavigation.Inverse.Name)));
// Note: we delayed setting the ConfigurationSource of SkipNavigation in HasMany()
// so we can test it here and override if the skip navigation was originally found
// by convention.
if (((IConventionSkipNavigation)SkipNavigation).GetConfigurationSource() == ConfigurationSource.Explicit)
{
throw new InvalidOperationException(
CoreStrings.ConflictingRelationshipNavigation(
SkipNavigation.DeclaringEntityType.DisplayName() + "." + SkipNavigation.Name,
RelatedEntityType.DisplayName() + (reference.Name == null
? ""
: "." + reference.Name),
SkipNavigation.DeclaringEntityType.DisplayName() + "." + SkipNavigation.Name,
SkipNavigation.TargetEntityType.DisplayName() + (SkipNavigation.Inverse == null
? ""
: "." + SkipNavigation.Inverse.Name)));
}

var navigationName = SkipNavigation.Name;
var declaringEntityType = (EntityType)DeclaringEntityType;
declaringEntityType.Model.Builder
.RemoveAssociationEntityIfCreatedImplicitly(
(EntityType)SkipNavigation.AssociationEntityType,
removeSkipNavigations: true,
ConfigurationSource.Explicit);

Builder = declaringEntityType.Builder
.HasRelationship(
(EntityType)RelatedEntityType,
navigationName,
ConfigurationSource.Explicit,
targetIsPrincipal: false);
SkipNavigation = null;
}

var foreignKey = Builder.Metadata;
Expand Down Expand Up @@ -205,11 +227,39 @@ public virtual CollectionCollectionBuilder WithMany([NotNull] string navigationN
}

var leftName = Builder?.Metadata.PrincipalToDependent.Name;
return new CollectionCollectionBuilder(
RelatedEntityType,
DeclaringEntityType,
WithLeftManyNavigation(navigationName),
WithRightManyNavigation(navigationName, leftName));
var collectionCollectionBuilder =
new CollectionCollectionBuilder(
RelatedEntityType,
DeclaringEntityType,
WithLeftManyNavigation(navigationName),
WithRightManyNavigation(navigationName, leftName));

Configure(collectionCollectionBuilder);

return 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]
protected virtual void Configure([NotNull] CollectionCollectionBuilder collectionCollectionBuilder)
{
Check.NotNull(collectionCollectionBuilder, nameof(collectionCollectionBuilder));

var leftSkipNavigation = (SkipNavigation)collectionCollectionBuilder.LeftNavigation;
var rightSkipNavigation = (SkipNavigation)collectionCollectionBuilder.RightNavigation;

leftSkipNavigation.Builder.HasInverse(rightSkipNavigation, ConfigurationSource.Explicit);

// Note: we delayed setting the ConfigurationSource of SkipNavigation
// in HasMany(). But now we know that both skip navigations should
// have ConfigurationSource.Explicit.
leftSkipNavigation.UpdateConfigurationSource(ConfigurationSource.Explicit);
rightSkipNavigation.UpdateConfigurationSource(ConfigurationSource.Explicit);
}

/// <summary>
Expand Down
30 changes: 20 additions & 10 deletions src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,16 @@ public virtual ReferenceCollectionBuilder<TEntity, TRelatedEntity> WithOne(
public new virtual CollectionCollectionBuilder<TRelatedEntity, TEntity> WithMany([NotNull] string navigationName)
{
var leftName = Builder?.Metadata.PrincipalToDependent.Name;
return new CollectionCollectionBuilder<TRelatedEntity, TEntity>(
RelatedEntityType,
DeclaringEntityType,
WithLeftManyNavigation(navigationName),
WithRightManyNavigation(navigationName, leftName));
var collectionCollectionBuilder =
new CollectionCollectionBuilder<TRelatedEntity, TEntity>(
RelatedEntityType,
DeclaringEntityType,
WithLeftManyNavigation(navigationName),
WithRightManyNavigation(navigationName, leftName));

Configure(collectionCollectionBuilder);

return collectionCollectionBuilder;
}

/// <summary>
Expand All @@ -126,11 +131,16 @@ public virtual CollectionCollectionBuilder<TRelatedEntity, TEntity> WithMany(
}

var leftName = Builder?.Metadata.PrincipalToDependent.Name;
return new CollectionCollectionBuilder<TRelatedEntity, TEntity>(
RelatedEntityType,
DeclaringEntityType,
WithLeftManyNavigation(navigationExpression.GetMemberAccess()),
WithRightManyNavigation(navigationExpression.GetMemberAccess(), leftName));
var collectionCollectionBuilder =
new CollectionCollectionBuilder<TRelatedEntity, TEntity>(
RelatedEntityType,
DeclaringEntityType,
WithLeftManyNavigation(navigationExpression.GetMemberAccess()),
WithRightManyNavigation(navigationExpression.GetMemberAccess(), leftName));

Configure(collectionCollectionBuilder);

return collectionCollectionBuilder;
}
}
}
4 changes: 4 additions & 0 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,10 @@ private CollectionNavigationBuilder HasMany(
string navigationName,
EntityType relatedEntityType)
{
// Note: delay setting ConfigurationSource of skip navigation (if it exists).
// We do not yet know whether this will be a HasMany().WithOne() or a
// HasMany().WithMany(). If the skip navigation was found by convention
// we want to be able to override it later.
var skipNavigation = navigationName != null ? Builder.Metadata.FindSkipNavigation(navigationName) : null;

InternalForeignKeyBuilder relationship = null;
Expand Down
10 changes: 10 additions & 0 deletions src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,11 @@ public virtual CollectionNavigationBuilder<TEntity, TRelatedEntity> HasMany<TRel
Check.NullButNotEmpty(navigationName, nameof(navigationName));

var relatedEntityType = FindRelatedEntityType(typeof(TRelatedEntity), navigationName);

// Note: delay setting ConfigurationSource of skip navigation (if it exists).
// We do not yet know whether this will be a HasMany().WithOne() or a
// HasMany().WithMany(). If the skip navigation was found by convention
// we want to be able to override it later.
var skipNavigation = navigationName != null ? Builder.Metadata.FindSkipNavigation(navigationName) : null;

InternalForeignKeyBuilder relationship = null;
Expand Down Expand Up @@ -751,6 +756,11 @@ public virtual CollectionNavigationBuilder<TEntity, TRelatedEntity> HasMany<TRel
{
var navigationMember = navigationExpression?.GetMemberAccess();
var relatedEntityType = FindRelatedEntityType(typeof(TRelatedEntity), navigationMember?.GetSimpleMemberName());

// Note: delay setting ConfigurationSource of skip navigation (if it exists).
// We do not yet know whether this will be a HasMany().WithOne() or a
// HasMany().WithMany(). If the skip navigation was found by convention
// we want to be able to override it later.
var skipNavigation = navigationMember != null ? Builder.Metadata.FindSkipNavigation(navigationMember) : null;

InternalForeignKeyBuilder relationship = null;
Expand Down
3 changes: 2 additions & 1 deletion src/EFCore/Metadata/Conventions/BackingFieldConvention.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ private FieldInfo GetFieldToSet(IConventionPropertyBase propertyBase)
{
if (propertyBase == null
|| !ConfigurationSource.Convention.Overrides(propertyBase.GetFieldInfoConfigurationSource())
|| propertyBase.IsIndexerProperty())
|| propertyBase.IsIndexerProperty()
|| propertyBase.IsShadowProperty())
{
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ public virtual void ProcessEntityTypeAdded(
&& t.FindDeclaredOwnership() == null
&& model.FindIsOwnedConfigurationSource(t.ClrType) == null
&& ((t.BaseType == null && clrType.IsAssignableFrom(t.ClrType))
|| (t.BaseType == entityType.BaseType && FindClosestBaseType(t) == entityType)))
|| (t.BaseType == entityType.BaseType && FindClosestBaseType(t) == entityType))
&& !t.HasSharedClrType)
.ToList();

foreach (var directlyDerivedType in directlyDerivedTypes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public interface ISkipNavigationInverseChangedConvention : IConvention
/// <param name="context"> Additional information associated with convention execution. </param>
void ProcessSkipNavigationInverseChanged(
[NotNull] IConventionSkipNavigationBuilder skipNavigationBuilder,
[NotNull] IConventionSkipNavigation inverse,
[NotNull] IConventionSkipNavigation oldInverse,
[CanBeNull] IConventionSkipNavigation inverse,
[CanBeNull] IConventionSkipNavigation oldInverse,
[NotNull] IConventionContext<IConventionSkipNavigation> context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ public virtual ConventionSet CreateConventionSet()

conventionSet.NavigationRemovedConventions.Add(relationshipDiscoveryConvention);

conventionSet.SkipNavigationAddedConventions.Add(new ManyToManyAssociationEntityTypeConvention(Dependencies));

conventionSet.IndexAddedConventions.Add(foreignKeyIndexConvention);

conventionSet.IndexRemovedConventions.Add(foreignKeyIndexConvention);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,6 @@ public override IConventionSkipNavigationBuilder OnSkipNavigationAdded(
{
if (navigationBuilder.Metadata.Builder == null)
{
Check.DebugAssert(false, "null builder");
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@ public virtual IConventionForeignKey OnSkipNavigationForeignKeyChanged(
/// </summary>
public virtual IConventionSkipNavigation OnSkipNavigationInverseChanged(
[NotNull] IConventionSkipNavigationBuilder navigationBuilder,
[NotNull] IConventionSkipNavigation inverse,
[NotNull] IConventionSkipNavigation oldInverse)
[CanBeNull] IConventionSkipNavigation inverse,
[CanBeNull] IConventionSkipNavigation oldInverse)
=> _scope.OnSkipNavigationInverseChanged(navigationBuilder, inverse, oldInverse);

/// <summary>
Expand Down
Loading

0 comments on commit 61f6cb9

Please sign in to comment.