Skip to content

Commit

Permalink
Add public APIs to configure shared type entities (#21552)
Browse files Browse the repository at this point in the history
Resolves #9914
  • Loading branch information
smitpatel committed Jul 18, 2020
1 parent ec73bd6 commit bf24a78
Show file tree
Hide file tree
Showing 31 changed files with 1,222 additions and 140 deletions.
12 changes: 12 additions & 0 deletions src/EFCore/Extensions/ConventionModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,18 @@ public static string AddIgnored([NotNull] this IConventionModel model, [NotNull]
Check.NotNull(clrType, nameof(clrType)),
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
/// Marks the given entity type as shared, indicating that when discovered matching entity types
/// should be configured as shared type entity type.
/// </summary>
/// <param name="model"> The model to add the shared type to. </param>
/// <param name="clrType"> The type of the entity type that should be shared. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
public static Type AddShared([NotNull] this IConventionModel model, [NotNull] Type clrType, bool fromDataAnnotation = false)
=> Check.NotNull((Model)model, nameof(model)).AddShared(
Check.NotNull(clrType, nameof(clrType)),
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
/// Forces post-processing on the model such that it is ready for use by the runtime. This post
/// processing happens automatically when using <see cref="DbContext.OnModelCreating" />; this method allows it to be run
Expand Down
10 changes: 10 additions & 0 deletions src/EFCore/Extensions/MutableModelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,16 @@ public static string RemoveOwned([NotNull] this IMutableModel model, [NotNull] T
=> Check.NotNull((Model)model, nameof(model)).RemoveOwned(
Check.NotNull(clrType, nameof(clrType)));

/// <summary>
/// Marks the given entity type as shared, indicating that when discovered matching entity types
/// should be configured as shared type entity type.
/// </summary>
/// <param name="model"> The model to add the shared type to. </param>
/// <param name="clrType"> The type of the entity type that should be shared. </param>
public static Type AddShared([NotNull] this IMutableModel model, [NotNull] Type clrType)
=> Check.NotNull((Model)model, nameof(model)).AddShared(
Check.NotNull(clrType, nameof(clrType)), ConfigurationSource.Explicit);

/// <summary>
/// Forces post-processing on the model such that it is ready for use by the runtime. This post
/// processing happens automatically when using <see cref="DbContext.OnModelCreating" />; this method allows it to be run
Expand Down
135 changes: 121 additions & 14 deletions src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,34 +86,108 @@ public CollectionCollectionBuilder(
/// <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="joinEntityType"> The CLR type of the join entity. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <param name="configureLeft"> The configuration for the relationship to the left entity type. </param>
/// <returns> The builder for the association type. </returns>
public virtual EntityTypeBuilder UsingEntity(
[NotNull] Type joinEntity,
[NotNull] Type joinEntityType,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureRight,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureLeft)
{
if (((Model)LeftEntityType.Model).IsShared(joinEntity))
Check.NotNull(joinEntityType, nameof(joinEntityType));
Check.NotNull(configureRight, nameof(configureRight));
Check.NotNull(configureLeft, nameof(configureLeft));

var existingAssociationEntityType = (EntityType)
(LeftNavigation.ForeignKey?.DeclaringEntityType
?? RightNavigation.ForeignKey?.DeclaringEntityType);
EntityType associationEntityType = null;
if (existingAssociationEntityType != null)
{
//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));
if (existingAssociationEntityType.ClrType == joinEntityType
&& !existingAssociationEntityType.HasSharedClrType)
{
associationEntityType = existingAssociationEntityType;
}
else
{
ModelBuilder.RemoveAssociationEntityIfCreatedImplicitly(
existingAssociationEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit);
}
}

if (associationEntityType == null)
{
associationEntityType = ModelBuilder.Entity(joinEntityType, ConfigurationSource.Explicit).Metadata;
}

var entityTypeBuilder = new EntityTypeBuilder(associationEntityType);

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="joinEntityName"> The name of the join entity. </param>
/// <param name="joinEntityType"> The CLR type of the join entity. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <param name="configureLeft"> The configuration for the relationship to the left entity type. </param>
/// <returns> The builder for the association type. </returns>
public virtual EntityTypeBuilder UsingEntity(
[NotNull] string joinEntityName,
[NotNull] Type joinEntityType,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureRight,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureLeft)
{
Check.NotEmpty(joinEntityName, nameof(joinEntityName));
Check.NotNull(joinEntityType, nameof(joinEntityType));
Check.NotNull(configureRight, nameof(configureRight));
Check.NotNull(configureLeft, nameof(configureLeft));

var existingAssociationEntityType = (EntityType)
(LeftNavigation.ForeignKey?.DeclaringEntityType
?? RightNavigation.ForeignKey?.DeclaringEntityType);
EntityType associationEntityType = null;
if (existingAssociationEntityType != null)
{
ModelBuilder.RemoveAssociationEntityIfCreatedImplicitly(
existingAssociationEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit);
if (existingAssociationEntityType.ClrType == joinEntityType
&& string.Equals(existingAssociationEntityType.Name, joinEntityName, StringComparison.Ordinal))
{
associationEntityType = existingAssociationEntityType;
}
else
{
ModelBuilder.RemoveAssociationEntityIfCreatedImplicitly(
existingAssociationEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit);
}
}

var entityTypeBuilder = new EntityTypeBuilder(
ModelBuilder.Entity(joinEntity, ConfigurationSource.Explicit).Metadata);
if (associationEntityType == null)
{
var existingEntityType = ModelBuilder.Metadata.FindEntityType(joinEntityName);
if (existingEntityType?.ClrType == joinEntityType)
{
associationEntityType = existingEntityType;
}
else
{
if (!ModelBuilder.Metadata.IsShared(joinEntityType))
{
throw new InvalidOperationException(CoreStrings.TypeNotMarkedAsShared(joinEntityType.DisplayName()));
}

associationEntityType = ModelBuilder.SharedEntity(joinEntityName, joinEntityType, ConfigurationSource.Explicit).Metadata;
}
}

var entityTypeBuilder = new EntityTypeBuilder(associationEntityType);

var leftForeignKey = configureLeft(entityTypeBuilder).Metadata;
var rightForeignKey = configureRight(entityTypeBuilder).Metadata;
Expand All @@ -126,18 +200,51 @@ public virtual EntityTypeBuilder UsingEntity(
/// <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="joinEntityType"> The CLR type of the join entity. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <param name="configureLeft"> The configuration for the relationship to the left 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] Type joinEntityType,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureRight,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureLeft,
[NotNull] Action<EntityTypeBuilder> configureAssociation)
{
var entityTypeBuilder = UsingEntity(joinEntity, configureRight, configureLeft);
Check.NotNull(joinEntityType, nameof(joinEntityType));
Check.NotNull(configureRight, nameof(configureRight));
Check.NotNull(configureLeft, nameof(configureLeft));
Check.NotNull(configureAssociation, nameof(configureAssociation));

var entityTypeBuilder = UsingEntity(joinEntityType, configureRight, configureLeft);
configureAssociation(entityTypeBuilder);

return new EntityTypeBuilder(RightEntityType);
}

/// <summary>
/// Configures the relationships to the entity types participating in the many-to-many relationship.
/// </summary>
/// <param name="joinEntityName"> The name of the join entity. </param>
/// <param name="joinEntityType"> The CLR type of the join entity. </param>
/// <param name="configureRight"> The configuration for the relationship to the right entity type. </param>
/// <param name="configureLeft"> The configuration for the relationship to the left 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] string joinEntityName,
[NotNull] Type joinEntityType,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureRight,
[NotNull] Func<EntityTypeBuilder, ReferenceCollectionBuilder> configureLeft,
[NotNull] Action<EntityTypeBuilder> configureAssociation)
{
Check.NotEmpty(joinEntityName, nameof(joinEntityName));
Check.NotNull(joinEntityType, nameof(joinEntityType));
Check.NotNull(configureRight, nameof(configureRight));
Check.NotNull(configureLeft, nameof(configureLeft));
Check.NotNull(configureAssociation, nameof(configureAssociation));

var entityTypeBuilder = UsingEntity(joinEntityName, joinEntityType, configureRight, configureLeft);
configureAssociation(entityTypeBuilder);

return new EntityTypeBuilder(RightEntityType);
Expand Down
Loading

0 comments on commit bf24a78

Please sign in to comment.