diff --git a/src/EFCore/Extensions/ConventionModelExtensions.cs b/src/EFCore/Extensions/ConventionModelExtensions.cs
index 05686a1f247..5ca00de8c6c 100644
--- a/src/EFCore/Extensions/ConventionModelExtensions.cs
+++ b/src/EFCore/Extensions/ConventionModelExtensions.cs
@@ -300,6 +300,18 @@ public static string AddIgnored([NotNull] this IConventionModel model, [NotNull]
Check.NotNull(clrType, nameof(clrType)),
fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ ///
+ /// Marks the given entity type as shared, indicating that when discovered matching entity types
+ /// should be configured as shared type entity type.
+ ///
+ /// The model to add the shared type to.
+ /// The type of the entity type that should be shared.
+ /// Indicates whether the configuration was specified using a data annotation.
+ 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);
+
///
/// Forces post-processing on the model such that it is ready for use by the runtime. This post
/// processing happens automatically when using ; this method allows it to be run
diff --git a/src/EFCore/Extensions/MutableModelExtensions.cs b/src/EFCore/Extensions/MutableModelExtensions.cs
index 2f77272b42b..a8a831cb11b 100644
--- a/src/EFCore/Extensions/MutableModelExtensions.cs
+++ b/src/EFCore/Extensions/MutableModelExtensions.cs
@@ -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)));
+ ///
+ /// Marks the given entity type as shared, indicating that when discovered matching entity types
+ /// should be configured as shared type entity type.
+ ///
+ /// The model to add the shared type to.
+ /// The type of the entity type that should be shared.
+ 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);
+
///
/// Forces post-processing on the model such that it is ready for use by the runtime. This post
/// processing happens automatically when using ; this method allows it to be run
diff --git a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
index e8a11ceb312..bb23ed53441 100644
--- a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
+++ b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder.cs
@@ -86,34 +86,108 @@ public CollectionCollectionBuilder(
///
/// Configures the relationships to the entity types participating in the many-to-many relationship.
///
- /// The type of the join entity.
+ /// The CLR type of the join entity.
/// The configuration for the relationship to the right entity type.
/// The configuration for the relationship to the left entity type.
/// The builder for the association type.
public virtual EntityTypeBuilder UsingEntity(
- [NotNull] Type joinEntity,
+ [NotNull] Type joinEntityType,
[NotNull] Func configureRight,
[NotNull] Func 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;
+ }
+
+ ///
+ /// Configures the relationships to the entity types participating in the many-to-many relationship.
+ ///
+ /// The name of the join entity.
+ /// The CLR type of the join entity.
+ /// The configuration for the relationship to the right entity type.
+ /// The configuration for the relationship to the left entity type.
+ /// The builder for the association type.
+ public virtual EntityTypeBuilder UsingEntity(
+ [NotNull] string joinEntityName,
+ [NotNull] Type joinEntityType,
+ [NotNull] Func configureRight,
+ [NotNull] Func 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;
@@ -126,18 +200,51 @@ public virtual EntityTypeBuilder UsingEntity(
///
/// Configures the relationships to the entity types participating in the many-to-many relationship.
///
- /// The type of the join entity.
+ /// The CLR type of the join entity.
/// The configuration for the relationship to the right entity type.
/// The configuration for the relationship to the left entity type.
/// The configuration of the association type.
/// The builder for the originating entity type so that multiple configuration calls can be chained.
public virtual EntityTypeBuilder UsingEntity(
- [NotNull] Type joinEntity,
+ [NotNull] Type joinEntityType,
[NotNull] Func configureRight,
[NotNull] Func configureLeft,
[NotNull] Action 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);
+ }
+
+ ///
+ /// Configures the relationships to the entity types participating in the many-to-many relationship.
+ ///
+ /// The name of the join entity.
+ /// The CLR type of the join entity.
+ /// The configuration for the relationship to the right entity type.
+ /// The configuration for the relationship to the left entity type.
+ /// The configuration of the association type.
+ /// The builder for the originating entity type so that multiple configuration calls can be chained.
+ public virtual EntityTypeBuilder UsingEntity(
+ [NotNull] string joinEntityName,
+ [NotNull] Type joinEntityType,
+ [NotNull] Func configureRight,
+ [NotNull] Func configureLeft,
+ [NotNull] Action 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);
diff --git a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
index b785a0efe2a..ed657f0c53b 100644
--- a/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/CollectionCollectionBuilder`.cs
@@ -6,6 +6,7 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Utilities;
namespace Microsoft.EntityFrameworkCore.Metadata.Builders
{
@@ -52,25 +53,97 @@ public virtual EntityTypeBuilder UsingEntity, ReferenceCollectionBuilder> configureLeft)
where TAssociationEntity : class
{
- if (((Model)LeftEntityType.Model).IsShared(typeof(TAssociationEntity)))
+ 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)
+ {
+ if (existingAssociationEntityType.ClrType == typeof(TAssociationEntity)
+ && !existingAssociationEntityType.HasSharedClrType)
+ {
+ associationEntityType = existingAssociationEntityType;
+ }
+ else
+ {
+ ModelBuilder.RemoveAssociationEntityIfCreatedImplicitly(
+ existingAssociationEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit);
+ }
+ }
+
+ if (associationEntityType == 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(typeof(TAssociationEntity).Name));
+ associationEntityType = ModelBuilder.Entity(typeof(TAssociationEntity), ConfigurationSource.Explicit).Metadata;
}
+ var entityTypeBuilder = new EntityTypeBuilder(associationEntityType);
+
+ var leftForeignKey = configureLeft(entityTypeBuilder).Metadata;
+ var rightForeignKey = configureRight(entityTypeBuilder).Metadata;
+
+ Using(rightForeignKey, leftForeignKey);
+
+ return entityTypeBuilder;
+ }
+
+ ///
+ /// Configures the relationships to the entity types participating in the many-to-many relationship.
+ ///
+ /// The name of the association entity.
+ /// The configuration for the relationship to the right entity type.
+ /// The configuration for the relationship to the left entity type.
+ /// The type of the association entity.
+ /// The builder for the association type.
+ public virtual EntityTypeBuilder UsingEntity(
+ [NotNull] string joinEntityName,
+ [NotNull] Func, ReferenceCollectionBuilder> configureRight,
+ [NotNull] Func, ReferenceCollectionBuilder> configureLeft)
+ where TAssociationEntity : class
+ {
+ Check.NotEmpty(joinEntityName, nameof(joinEntityName));
+ 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 == typeof(TAssociationEntity)
+ && string.Equals(existingAssociationEntityType.Name, joinEntityName, StringComparison.Ordinal))
+ {
+ associationEntityType = existingAssociationEntityType;
+ }
+ else
+ {
+ ModelBuilder.RemoveAssociationEntityIfCreatedImplicitly(
+ existingAssociationEntityType, removeSkipNavigations: false, ConfigurationSource.Explicit);
+ }
}
- var entityTypeBuilder = new EntityTypeBuilder(
- ModelBuilder.Entity(typeof(TAssociationEntity), ConfigurationSource.Explicit).Metadata);
+ if (associationEntityType == null)
+ {
+ var existingEntityType = ModelBuilder.Metadata.FindEntityType(joinEntityName);
+ if (existingEntityType?.ClrType == typeof(TAssociationEntity))
+ {
+ associationEntityType = existingEntityType;
+ }
+ else
+ {
+ if (!ModelBuilder.Metadata.IsShared(typeof(TAssociationEntity)))
+ {
+ throw new InvalidOperationException(CoreStrings.TypeNotMarkedAsShared(typeof(TAssociationEntity).DisplayName()));
+ }
+
+ associationEntityType = ModelBuilder.SharedEntity(joinEntityName, typeof(TAssociationEntity), ConfigurationSource.Explicit).Metadata;
+ }
+ }
+
+ var entityTypeBuilder = new EntityTypeBuilder(associationEntityType);
var leftForeignKey = configureLeft(entityTypeBuilder).Metadata;
var rightForeignKey = configureRight(entityTypeBuilder).Metadata;
@@ -94,7 +167,37 @@ public virtual EntityTypeBuilder UsingEntity(
[NotNull] Action> configureAssociation)
where TAssociationEntity : class
{
- var entityTypeBuilder = UsingEntity(configureRight, configureLeft);
+ Check.NotNull(configureRight, nameof(configureRight));
+ Check.NotNull(configureLeft, nameof(configureLeft));
+
+ var entityTypeBuilder = UsingEntity(configureRight, configureLeft);
+ configureAssociation(entityTypeBuilder);
+
+ return new EntityTypeBuilder(RightEntityType);
+ }
+
+ ///
+ /// Configures the relationships to the entity types participating in the many-to-many relationship.
+ ///
+ /// The name of the association entity.
+ /// The configuration for the relationship to the right entity type.
+ /// The configuration for the relationship to the left entity type.
+ /// The configuration of the association type.
+ /// The type of the association entity.
+ /// The builder for the originating entity type so that multiple configuration calls can be chained.
+ public virtual EntityTypeBuilder UsingEntity(
+ [NotNull] string joinEntityName,
+ [NotNull] Func, ReferenceCollectionBuilder> configureRight,
+ [NotNull] Func, ReferenceCollectionBuilder> configureLeft,
+ [NotNull] Action> configureAssociation)
+ where TAssociationEntity : class
+ {
+ Check.NotEmpty(joinEntityName, nameof(joinEntityName));
+ Check.NotNull(configureRight, nameof(configureRight));
+ Check.NotNull(configureLeft, nameof(configureLeft));
+ Check.NotNull(configureAssociation, nameof(configureAssociation));
+
+ var entityTypeBuilder = UsingEntity(joinEntityName, configureRight, configureLeft);
configureAssociation(entityTypeBuilder);
return new EntityTypeBuilder(RightEntityType);
diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
index 7ccb3d02088..8e3ac1a2cff 100644
--- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs
@@ -376,7 +376,7 @@ public virtual EntityTypeBuilder OwnsOne(
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsOneBuilder(new TypeIdentity(ownedTypeName), navigationName));
+ buildAction(OwnsOneBuilder(new TypeIdentity(ownedTypeName), navigationName));
return this;
}
@@ -412,7 +412,7 @@ public virtual EntityTypeBuilder OwnsOne(
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsOneBuilder(new TypeIdentity(ownedType, (Model)Metadata.Model), navigationName));
+ buildAction(OwnsOneBuilder(new TypeIdentity(ownedType, (Model)Metadata.Model), navigationName));
return this;
}
@@ -523,7 +523,7 @@ public virtual EntityTypeBuilder OwnsMany(
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsManyBuilder(new TypeIdentity(ownedTypeName), navigationName));
+ buildAction(OwnsManyBuilder(new TypeIdentity(ownedTypeName), navigationName));
return this;
}
@@ -559,7 +559,7 @@ public virtual EntityTypeBuilder OwnsMany(
Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsManyBuilder(new TypeIdentity(ownedType, (Model)Metadata.Model), navigationName));
+ buildAction(OwnsManyBuilder(new TypeIdentity(ownedType, (Model)Metadata.Model), navigationName));
return this;
}
diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
index d5a00ae7610..e3339372c96 100644
--- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs
@@ -346,8 +346,7 @@ public virtual OwnedNavigationBuilder OwnsOne OwnsOne(
[NotNull] Expression> navigationExpression)
where TRelatedEntity : class
- => OwnsOneBuilder(
- new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess()));
+ => OwnsOneBuilder(new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess()));
///
///
@@ -380,7 +379,7 @@ public virtual EntityTypeBuilder OwnsOne(
Check.NotNull(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsOneBuilder(new MemberIdentity(navigationName)));
+ buildAction(OwnsOneBuilder(new MemberIdentity(navigationName)));
return this;
}
@@ -416,7 +415,7 @@ public virtual EntityTypeBuilder OwnsOne(
Check.NotNull(navigationExpression, nameof(navigationExpression));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsOneBuilder(new MemberIdentity(navigationExpression.GetMemberAccess())));
+ buildAction(OwnsOneBuilder(new MemberIdentity(navigationExpression.GetMemberAccess())));
return this;
}
@@ -426,9 +425,8 @@ private OwnedNavigationBuilder OwnsOneBuilder OwnsMany OwnsMany(
[NotNull] Expression>> navigationExpression)
where TRelatedEntity : class
- => OwnsManyBuilder(
- new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess()));
+ => OwnsManyBuilder(new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess()));
///
///
@@ -523,10 +520,10 @@ public virtual EntityTypeBuilder OwnsMany(
[NotNull] Action> buildAction)
where TRelatedEntity : class
{
- Check.NotNull(navigationName, nameof(navigationName));
+ Check.NotEmpty(navigationName, nameof(navigationName));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsManyBuilder(new MemberIdentity(navigationName)));
+ buildAction(OwnsManyBuilder(new MemberIdentity(navigationName)));
return this;
}
@@ -562,7 +559,7 @@ public virtual EntityTypeBuilder OwnsMany(
Check.NotNull(navigationExpression, nameof(navigationExpression));
Check.NotNull(buildAction, nameof(buildAction));
- buildAction.Invoke(OwnsManyBuilder(new MemberIdentity(navigationExpression.GetMemberAccess())));
+ buildAction(OwnsManyBuilder(new MemberIdentity(navigationExpression.GetMemberAccess())));
return this;
}
@@ -572,9 +569,8 @@ private OwnedNavigationBuilder OwnsManyBuilder
IConventionEntityTypeBuilder Entity([NotNull] string name, bool? shouldBeOwned = false, bool fromDataAnnotation = false);
+ ///
+ ///
+ /// Returns an object that can be used to configure a given shared type entity type in the model.
+ ///
+ ///
+ /// If an entity type with the provided name is not already part of the model, a new entity type with provided CLR
+ /// type will be added to the model as shared type entity type.
+ ///
+ ///
+ /// Shared type entity type is an entity type which can share CLR type with other types in the model but has
+ /// a unique name and always identified by the name.
+ ///
+ ///
+ /// The name of the entity type to be configured.
+ /// The type of the entity type to be configured.
+ ///
+ /// if the entity type should be owned,
+ /// if the entity type should not be owned
+ ///
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// An object that can be used to configure the entity type if the entity type was added or already part of the model,
+ /// otherwise.
+ ///
+ IConventionEntityTypeBuilder SharedEntity([NotNull] string name, [NotNull] Type type, bool? shouldBeOwned = false, bool fromDataAnnotation = false);
+
///
/// Returns an object that can be used to configure a given entity type in the model.
/// If an entity type with the provided type is not already part of the model,
diff --git a/src/EFCore/Metadata/Builders/IConventionSharedEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionSharedEntityTypeBuilder.cs
new file mode 100644
index 00000000000..7674d56ac2a
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/IConventionSharedEntityTypeBuilder.cs
@@ -0,0 +1,13 @@
+// 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.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders
+{
+ ///
+ /// This interface is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ public interface IConventionSharedEntityTypeBuilder
+ {
+ }
+}
diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
index b35f18ae957..d56be9f980a 100644
--- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
+++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder.cs
@@ -397,7 +397,7 @@ public virtual OwnedNavigationBuilder OwnsOne(
using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
{
- buildAction.Invoke(OwnsOneBuilder(new TypeIdentity(ownedTypeName), navigationName));
+ buildAction(OwnsOneBuilder(new TypeIdentity(ownedTypeName), navigationName));
return this;
}
}
@@ -437,7 +437,7 @@ public virtual OwnedNavigationBuilder OwnsOne(
using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
{
- buildAction.Invoke(OwnsOneBuilder(new TypeIdentity(ownedType, (Model)OwnedEntityType.Model), navigationName));
+ buildAction(OwnsOneBuilder(new TypeIdentity(ownedType, (Model)OwnedEntityType.Model), navigationName));
return this;
}
}
@@ -551,7 +551,7 @@ public virtual OwnedNavigationBuilder OwnsMany(
using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
{
- buildAction.Invoke(OwnsManyBuilder(new TypeIdentity(ownedTypeName), navigationName));
+ buildAction(OwnsManyBuilder(new TypeIdentity(ownedTypeName), navigationName));
return this;
}
}
@@ -590,7 +590,7 @@ public virtual OwnedNavigationBuilder OwnsMany(
using (DependentEntityType.Model.ConventionDispatcher.DelayConventions())
{
- buildAction.Invoke(OwnsManyBuilder(new TypeIdentity(ownedType, DependentEntityType.Model), navigationName));
+ buildAction(OwnsManyBuilder(new TypeIdentity(ownedType, DependentEntityType.Model), navigationName));
return this;
}
}
diff --git a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
index 33b4177954a..22d4f130972 100644
--- a/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
+++ b/src/EFCore/Metadata/Builders/OwnedNavigationBuilder`.cs
@@ -284,8 +284,7 @@ public virtual OwnedNavigationBuilder Own
[NotNull] Expression> navigationExpression)
where TNewDependentEntity : class
=> OwnsOneBuilder(
- new MemberIdentity(
- Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess()));
+ new MemberIdentity(Check.NotNull(navigationExpression, nameof(navigationExpression)).GetMemberAccess()));
///
///
@@ -319,7 +318,7 @@ public virtual OwnedNavigationBuilder OwnsOne(new MemberIdentity(navigationName)));
+ buildAction(OwnsOneBuilder(new MemberIdentity(navigationName)));
return this;
}
@@ -356,7 +355,7 @@ public virtual OwnedNavigationBuilder OwnsOne(new MemberIdentity(navigationExpression.GetMemberAccess())));
+ buildAction(OwnsOneBuilder(new MemberIdentity(navigationExpression.GetMemberAccess())));
return this;
}
@@ -367,10 +366,8 @@ private OwnedNavigationBuilder OwnsOneBui
InternalForeignKeyBuilder relationship;
using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions())
{
- relationship = navigation.MemberInfo == null
- ? DependentEntityType.Builder.HasOwnership(typeof(TNewDependentEntity), navigation.Name, ConfigurationSource.Explicit)
- : DependentEntityType.Builder.HasOwnership(
- typeof(TNewDependentEntity), (PropertyInfo)navigation.MemberInfo, ConfigurationSource.Explicit);
+ relationship = DependentEntityType.Builder.HasOwnership(typeof(TNewDependentEntity), navigation, ConfigurationSource.Explicit);
+
relationship.IsUnique(true, ConfigurationSource.Explicit);
relationship = (InternalForeignKeyBuilder)batch.Run(relationship.Metadata).Builder;
}
@@ -470,7 +467,7 @@ public virtual OwnedNavigationBuilder OwnsMany(new MemberIdentity(navigationName)));
+ buildAction(OwnsManyBuilder(new MemberIdentity(navigationName)));
return this;
}
}
@@ -509,7 +506,7 @@ public virtual OwnedNavigationBuilder OwnsMany(new MemberIdentity(navigationExpression.GetMemberAccess())));
+ buildAction(OwnsManyBuilder(new MemberIdentity(navigationExpression.GetMemberAccess())));
return this;
}
}
@@ -520,10 +517,8 @@ private OwnedNavigationBuilder OwnsManyBuil
InternalForeignKeyBuilder relationship;
using (var batch = DependentEntityType.Model.ConventionDispatcher.DelayConventions())
{
- relationship = navigation.MemberInfo == null
- ? DependentEntityType.Builder.HasOwnership(typeof(TNewRelatedEntity), navigation.Name, ConfigurationSource.Explicit)
- : DependentEntityType.Builder.HasOwnership(
- typeof(TNewRelatedEntity), (PropertyInfo)navigation.MemberInfo, ConfigurationSource.Explicit);
+ relationship = DependentEntityType.Builder.HasOwnership(typeof(TNewRelatedEntity), navigation, ConfigurationSource.Explicit);
+
relationship.IsUnique(false, ConfigurationSource.Explicit);
relationship = (InternalForeignKeyBuilder)batch.Run(relationship.Metadata).Builder;
}
diff --git a/src/EFCore/Metadata/Builders/SharedEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/SharedEntityTypeBuilder.cs
new file mode 100644
index 00000000000..13fe5a83bbf
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/SharedEntityTypeBuilder.cs
@@ -0,0 +1,54 @@
+// 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.ComponentModel;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders
+{
+ ///
+ ///
+ /// Instances of this class are returned from methods when using the API
+ /// and it is not designed to be directly constructed in your application code.
+ ///
+ ///
+ public class SharedEntityTypeBuilder
+ {
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public SharedEntityTypeBuilder()
+ {
+ }
+
+ #region Hidden System.Object members
+
+ ///
+ /// Returns a string that represents the current object.
+ ///
+ /// A string that represents the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ToString() => base.ToString();
+
+ ///
+ /// Determines whether the specified object is equal to the current object.
+ ///
+ /// The object to compare with the current object.
+ /// if the specified object is equal to the current object; otherwise, .
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj) => base.Equals(obj);
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ /// A hash code for the current object.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => base.GetHashCode();
+
+ #endregion
+ }
+}
diff --git a/src/EFCore/Metadata/Builders/SharedEntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/SharedEntityTypeBuilder`.cs
new file mode 100644
index 00000000000..8abb3b920a5
--- /dev/null
+++ b/src/EFCore/Metadata/Builders/SharedEntityTypeBuilder`.cs
@@ -0,0 +1,29 @@
+// 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 Microsoft.EntityFrameworkCore.Infrastructure;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Builders
+{
+ ///
+ ///
+ /// Instances of this class are returned from methods when using the API
+ /// and it is not designed to be directly constructed in your application code.
+ ///
+ ///
+ /// The entity type being configured.
+ // ReSharper disable once UnusedTypeParameter
+ public class SharedEntityTypeBuilder : SharedEntityTypeBuilder
+ {
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public SharedEntityTypeBuilder()
+ {
+ }
+ }
+}
diff --git a/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs b/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs
index 02026872d57..6e766f8dd67 100644
--- a/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs
+++ b/src/EFCore/Metadata/Conventions/ManyToManyAssociationEntityTypeConvention.cs
@@ -106,12 +106,9 @@ private void CreateAssociationEntityType(
inverseEntityType.ShortName()),
otherIdentifiers,
int.MaxValue);
- //TODO #9914 - when the shared-type entity type version of model.Entity() is available call that instead
- var associationEntityTypeBuilder =
- model.AddEntityType(
- associationEntityTypeName,
- Model.DefaultPropertyBagType,
- ConfigurationSource.Convention).Builder;
+
+ var associationEntityTypeBuilder = model.Builder.SharedEntity(
+ associationEntityTypeName, Model.DefaultPropertyBagType, ConfigurationSource.Convention);
// Create left and right foreign keys from the outer entity types to
// the association entity type and configure the skip navigations.
diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
index 1882acc1b65..b3b3eb7493c 100644
--- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs
@@ -2952,8 +2952,7 @@ public virtual InternalForeignKeyBuilder HasOwnership(
[NotNull] string navigationName,
ConfigurationSource configurationSource)
=> HasOwnership(
- new TypeIdentity(targetEntityTypeName), MemberIdentity.Create(navigationName),
- inverse: null, configurationSource);
+ new TypeIdentity(targetEntityTypeName), MemberIdentity.Create(navigationName), inverse: null, configurationSource);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -2966,8 +2965,7 @@ public virtual InternalForeignKeyBuilder HasOwnership(
[NotNull] string navigationName,
ConfigurationSource configurationSource)
=> HasOwnership(
- new TypeIdentity(targetEntityType, Metadata.Model), MemberIdentity.Create(navigationName),
- inverse: null, configurationSource);
+ new TypeIdentity(targetEntityType, Metadata.Model), MemberIdentity.Create(navigationName), inverse: null, configurationSource);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -2980,8 +2978,20 @@ public virtual InternalForeignKeyBuilder HasOwnership(
[NotNull] MemberInfo navigationMember,
ConfigurationSource configurationSource)
=> HasOwnership(
- new TypeIdentity(targetEntityType, Metadata.Model), MemberIdentity.Create(navigationMember),
- inverse: null, configurationSource);
+ new TypeIdentity(targetEntityType, Metadata.Model), MemberIdentity.Create(navigationMember), inverse: null, configurationSource);
+
+ ///
+ /// 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.
+ ///
+ public virtual InternalForeignKeyBuilder HasOwnership(
+ [NotNull] Type targetEntityType,
+ MemberIdentity navigation,
+ ConfigurationSource configurationSource)
+ => HasOwnership(
+ new TypeIdentity(targetEntityType, Metadata.Model), navigation, inverse: null, configurationSource);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -3033,6 +3043,7 @@ private InternalForeignKeyBuilder HasOwnership(
if (existingNavigation.TargetEntityType.Name == targetEntityType.Name)
{
var existingOwnedEntityType = existingNavigation.ForeignKey.DeclaringEntityType;
+ // Upgrade configurationSource for existing entity type
if (existingOwnedEntityType.HasDefiningNavigation())
{
if (targetEntityType.Type != null)
@@ -4244,7 +4255,8 @@ private bool CanAddDiscriminatorProperty(
///
IConventionEntityType IConventionEntityTypeBuilder.Metadata
{
- [DebuggerStepThrough] get => Metadata;
+ [DebuggerStepThrough]
+ get => Metadata;
}
///
diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs
index 95fbf275a8c..54706f1c342 100644
--- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs
@@ -48,7 +48,17 @@ public InternalModelBuilder([NotNull] Model metadata)
///
public virtual InternalEntityTypeBuilder Entity(
[NotNull] string name, ConfigurationSource configurationSource, bool? shouldBeOwned = false)
- => Entity(new TypeIdentity(name), configurationSource, shouldBeOwned);
+ => Entity(new TypeIdentity(name), null, configurationSource, shouldBeOwned);
+
+ ///
+ /// 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.
+ ///
+ public virtual InternalEntityTypeBuilder SharedEntity(
+ [NotNull] string name, [NotNull] Type type, ConfigurationSource configurationSource, bool? shouldBeOwned = false)
+ => Entity(new TypeIdentity(name), Check.NotNull(type, nameof(type)), configurationSource, shouldBeOwned);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -58,10 +68,10 @@ public virtual InternalEntityTypeBuilder Entity(
///
public virtual InternalEntityTypeBuilder Entity(
[NotNull] Type type, ConfigurationSource configurationSource, bool? shouldBeOwned = false)
- => Entity(new TypeIdentity(type, Metadata), configurationSource, shouldBeOwned);
+ => Entity(new TypeIdentity(type, Metadata), null, configurationSource, shouldBeOwned);
private InternalEntityTypeBuilder Entity(
- in TypeIdentity type, ConfigurationSource configurationSource, bool? shouldBeOwned)
+ in TypeIdentity type, Type sharedTypeClrType, ConfigurationSource configurationSource, bool? shouldBeOwned)
{
if (IsIgnored(type, configurationSource))
{
@@ -69,14 +79,35 @@ private InternalEntityTypeBuilder Entity(
}
var clrType = type.Type;
- var entityType = clrType == null
- ? Metadata.FindEntityType(type.Name)
- : Metadata.FindEntityType(clrType);
+ EntityType entityType;
+ if (clrType != null)
+ {
+ if (Metadata.IsShared(clrType))
+ {
+ return configurationSource == ConfigurationSource.Explicit
+ ? throw new InvalidOperationException(CoreStrings.ClashingSharedType(clrType.DisplayName()))
+ : (InternalEntityTypeBuilder)null;
+ }
+
+ entityType = Metadata.FindEntityType(clrType);
+ }
+ else
+ {
+ if (sharedTypeClrType != null && Metadata.FindEntityType(Metadata.GetDisplayName(sharedTypeClrType)) != null)
+ {
+ return configurationSource == ConfigurationSource.Explicit
+ ? throw new InvalidOperationException(CoreStrings.ClashingNonSharedType(type.Name))
+ : (InternalEntityTypeBuilder)null;
+ }
+
+ entityType = Metadata.FindEntityType(type.Name);
+ }
if (shouldBeOwned == false
- && (ShouldBeOwnedType(type)
- || entityType != null && entityType.IsOwned()))
+ && (ShouldBeOwnedType(type) // Marked in model as owned
+ || entityType != null && entityType.IsOwned())) // Created using Owns* API
{
+ // We always throw as configuring a type as owned is always comes from user (through Explicit/DataAnnotation)
throw new InvalidOperationException(
CoreStrings.ClashingOwnedEntityType(
clrType == null ? type.Name : clrType.ShortDisplayName()));
@@ -106,14 +137,24 @@ private InternalEntityTypeBuilder Entity(
if (entityType != null)
{
+ if (sharedTypeClrType != null)
+ {
+ if (entityType.ClrType != sharedTypeClrType)
+ {
+ throw new InvalidOperationException(CoreStrings.ClashingMismatchedSharedType(type.Name));
+ }
+ }
+
entityType.UpdateConfigurationSource(configurationSource);
return entityType.Builder;
}
Metadata.RemoveIgnored(type.Name);
- entityType = clrType == null
- ? Metadata.AddEntityType(type.Name, configurationSource)
- : Metadata.AddEntityType(clrType, configurationSource);
+ entityType = clrType != null
+ ? Metadata.AddEntityType(clrType, configurationSource)
+ : sharedTypeClrType != null
+ ? Metadata.AddEntityType(type.Name, sharedTypeClrType, configurationSource)
+ : Metadata.AddEntityType(type.Name, configurationSource);
return entityType?.Builder;
}
@@ -307,6 +348,34 @@ public virtual IConventionOwnedEntityTypeBuilder Owned(
private bool ShouldBeOwnedType(in TypeIdentity type)
=> type.Type != null && Metadata.IsOwned(type.Type);
+ ///
+ /// 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.
+ ///
+ public virtual IConventionSharedEntityTypeBuilder SharedEntity(
+ [NotNull] Type type, ConfigurationSource configurationSource)
+ {
+ if (IsIgnored(type, configurationSource))
+ {
+ return null;
+ }
+
+ Metadata.RemoveIgnored(type);
+
+ foreach (var entityType in Metadata.GetEntityTypes()
+ .Where(et => !et.HasSharedClrType && et.ClrType == type && configurationSource.Overrides(et.GetConfigurationSource()))
+ .ToList())
+ {
+ HasNoEntityType(entityType, configurationSource);
+ }
+
+ Metadata.AddShared(type);
+
+ return new InternalSharedEntityTypeBuilder();
+ }
+
///
/// 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
@@ -560,7 +629,8 @@ public virtual bool CanSetPropertyAccessMode(
///
IConventionModel IConventionModelBuilder.Metadata
{
- [DebuggerStepThrough] get => Metadata;
+ [DebuggerStepThrough]
+ get => Metadata;
}
///
@@ -573,6 +643,16 @@ IConventionModel IConventionModelBuilder.Metadata
IConventionEntityTypeBuilder IConventionModelBuilder.Entity(string name, bool? shouldBeOwned, bool fromDataAnnotation)
=> Entity(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, shouldBeOwned);
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ IConventionEntityTypeBuilder IConventionModelBuilder.SharedEntity(string name, Type type, bool? shouldBeOwned, bool fromDataAnnotation)
+ => SharedEntity(name, type, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, shouldBeOwned);
+
///
/// 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
diff --git a/src/EFCore/Metadata/Internal/InternalSharedEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalSharedEntityTypeBuilder.cs
new file mode 100644
index 00000000000..849619e4f46
--- /dev/null
+++ b/src/EFCore/Metadata/Internal/InternalSharedEntityTypeBuilder.cs
@@ -0,0 +1,17 @@
+// 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 Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Internal
+{
+ ///
+ /// 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.
+ ///
+ public class InternalSharedEntityTypeBuilder : IConventionSharedEntityTypeBuilder
+ {
+ }
+}
diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs
index 922e797674d..beec03812b4 100644
--- a/src/EFCore/Metadata/Internal/Model.cs
+++ b/src/EFCore/Metadata/Internal/Model.cs
@@ -58,7 +58,7 @@ private readonly SortedDictionary> _entityTypesWit
private readonly Dictionary _ignoredTypeNames
= new Dictionary(StringComparer.Ordinal);
- private readonly HashSet _sharedEntityClrTypes = new HashSet();
+ private readonly Dictionary _sharedEntityClrTypes = new Dictionary();
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -235,9 +235,17 @@ private EntityType AddEntityType(EntityType entityType)
throw new InvalidOperationException(CoreStrings.ClashingNonSharedType(entityType.DisplayName()));
}
- _sharedEntityClrTypes.Add(entityType.ClrType);
+ if (_sharedEntityClrTypes.TryGetValue(entityType.ClrType, out var existingConfigurationSource))
+ {
+ _sharedEntityClrTypes[entityType.ClrType] = entityType.GetConfigurationSource().Max(existingConfigurationSource);
+ }
+ else
+ {
+ _sharedEntityClrTypes.Add(entityType.ClrType, entityType.GetConfigurationSource());
+ }
}
- else if (_sharedEntityClrTypes.Contains(entityType.ClrType))
+ else if (entityType.ClrType != null
+ && _sharedEntityClrTypes.ContainsKey(entityType.ClrType))
{
throw new InvalidOperationException(CoreStrings.ClashingSharedType(entityType.DisplayName()));
}
@@ -772,7 +780,7 @@ public virtual bool IsIgnored([NotNull] Type type)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual bool IsShared([NotNull] Type type)
- => _sharedEntityClrTypes.Contains(type);
+ => _sharedEntityClrTypes.ContainsKey(type);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -877,6 +885,31 @@ public virtual string RemoveOwned([NotNull] Type clrType)
return ownedTypes.Remove(name) ? name : null;
}
+ ///
+ /// 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.
+ ///
+ public virtual Type AddShared([NotNull] Type clrType, ConfigurationSource configurationSource)
+ {
+ if (_entityTypes.Any(et => !et.Value.HasSharedClrType && et.Value.ClrType == clrType))
+ {
+ throw new InvalidOperationException(CoreStrings.CannotMarkShared(clrType.ShortDisplayName()));
+ }
+
+ if (_sharedEntityClrTypes.TryGetValue(clrType, out var existingConfigurationSource))
+ {
+ _sharedEntityClrTypes[clrType] = configurationSource.Max(existingConfigurationSource);
+ }
+ else
+ {
+ _sharedEntityClrTypes.Add(clrType, configurationSource);
+ }
+
+ return clrType;
+ }
+
///
/// 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
diff --git a/src/EFCore/ModelBuilder.cs b/src/EFCore/ModelBuilder.cs
index 111e7e0be7a..4725a3c4246 100644
--- a/src/EFCore/ModelBuilder.cs
+++ b/src/EFCore/ModelBuilder.cs
@@ -115,6 +115,30 @@ public virtual EntityTypeBuilder Entity()
where TEntity : class
=> new EntityTypeBuilder(Builder.Entity(typeof(TEntity), ConfigurationSource.Explicit).Metadata);
+ ///
+ ///
+ /// Returns an object that can be used to configure a given shared type entity type in the model.
+ ///
+ ///
+ /// If an entity type with the provided name is not already part of the model, a new entity type with provided CLR
+ /// type will be added to the model as shared type entity type.
+ ///
+ ///
+ /// Shared type entity type is an entity type which can share CLR type with other types in the model but has
+ /// a unique name and always identified by the name.
+ ///
+ ///
+ /// The CLR type of the entity type to be configured.
+ /// The name of the entity type to be configured.
+ /// An object that can be used to configure the entity type.
+ public virtual EntityTypeBuilder SharedEntity([NotNull] string name)
+ where TEntity : class
+ {
+ Check.NotEmpty(name, nameof(name));
+
+ return new EntityTypeBuilder(Builder.SharedEntity(name, typeof(TEntity), ConfigurationSource.Explicit).Metadata);
+ }
+
///
/// Returns an object that can be used to configure a given entity type in the model.
/// If the entity type is not already part of the model, it will be added to the model.
@@ -142,6 +166,30 @@ public virtual EntityTypeBuilder Entity([NotNull] string name)
return new EntityTypeBuilder(Builder.Entity(name, ConfigurationSource.Explicit).Metadata);
}
+ ///
+ ///
+ /// Returns an object that can be used to configure a given shared type entity type in the model.
+ ///
+ ///
+ /// If an entity type with the provided name is not already part of the model, a new entity type with provided CLR
+ /// type will be added to the model as shared type entity type.
+ ///
+ ///
+ /// Shared type entity type is an entity type which can share CLR type with other types in the model but has
+ /// a unique name and always identified by the name.
+ ///
+ ///
+ /// The name of the entity type to be configured.
+ /// The CLR type of the entity type to be configured.
+ /// An object that can be used to configure the entity type.
+ public virtual EntityTypeBuilder SharedEntity([NotNull] string name, [NotNull] Type clrType)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotNull(clrType, nameof(clrType));
+
+ return new EntityTypeBuilder(Builder.SharedEntity(name, clrType, ConfigurationSource.Explicit).Metadata);
+ }
+
///
///
/// Performs configuration of a given entity type in the model. If the entity type is not already part
@@ -168,6 +216,41 @@ public virtual ModelBuilder Entity([NotNull] Action
+ ///
+ /// Returns an object that can be used to configure a given shared type entity type in the model.
+ ///
+ ///
+ /// If an entity type with the provided name is not already part of the model, a new entity type with provided CLR
+ /// type will be added to the model as shared type entity type.
+ ///
+ ///
+ /// Shared type entity type is an entity type which can share CLR type with other types in the model but has
+ /// a unique name and always identified by the name.
+ ///
+ ///
+ /// This overload allows configuration of the entity type to be done inline in the method call rather
+ /// than being chained after a call to . This allows additional
+ /// configuration at the model level to be chained after configuration for the entity type.
+ ///
+ ///
+ /// The CLR type of the entity type to be configured.
+ /// The name of the entity type to be configured.
+ /// An action that performs configuration of the entity type.
+ ///
+ /// The same instance so that additional configuration calls can be chained.
+ ///
+ public virtual ModelBuilder SharedEntity([NotNull] string name, [NotNull] Action> buildAction)
+ where TEntity : class
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ buildAction(SharedEntity(name));
+
+ return this;
+ }
+
///
///
/// Performs configuration of a given entity type in the model. If the entity type is not already part
@@ -221,11 +304,46 @@ public virtual ModelBuilder Entity([NotNull] string name, [NotNull] Action
+ ///
+ /// Returns an object that can be used to configure a given shared type entity type in the model.
+ ///
+ ///
+ /// If an entity type with the provided name is not already part of the model, a new entity type with provided CLR
+ /// type will be added to the model as shared type entity type.
+ ///
+ ///
+ /// Shared type entity type is an entity type which can share CLR type with other types in the model but has
+ /// a unique name and always identified by the name.
+ ///
+ ///
+ /// This overload allows configuration of the entity type to be done in line in the method call rather
+ /// than being chained after a call to . This allows additional
+ /// configuration at the model level to be chained after configuration for the entity type.
+ ///
+ ///
+ /// The name of the entity type to be configured.
+ /// The CLR type of the entity type to be configured.
+ /// An action that performs configuration of the entity type.
+ ///
+ /// The same instance so that additional configuration calls can be chained.
+ ///
+ public virtual ModelBuilder SharedEntity([NotNull] string name, [NotNull] Type clrType, [NotNull] Action buildAction)
+ {
+ Check.NotEmpty(name, nameof(name));
+ Check.NotNull(clrType, nameof(clrType));
+ Check.NotNull(buildAction, nameof(buildAction));
+
+ buildAction(SharedEntity(name, clrType));
+
+ return this;
+ }
+
///
/// Excludes the given entity type from the model. This method is typically used to remove types from
/// the model that were added by convention.
///
- /// The entity type to be removed from the model.
+ /// The entity type to be removed from the model.
///
/// The same instance so that additional configuration calls can be chained.
///
@@ -234,7 +352,7 @@ public virtual ModelBuilder Ignore()
=> Ignore(typeof(TEntity));
///
- /// Excludes the given entity type from the model. This method is typically used to remove types from
+ /// Excludes an entity type with given CLR type from the model. This method is typically used to remove types from
/// the model that were added by convention.
///
/// The entity type to be removed from the model.
@@ -250,6 +368,23 @@ public virtual ModelBuilder Ignore([NotNull] Type type)
return this;
}
+ ///
+ /// Excludes an entity type with the given name from the model. This method is typically used to remove types from
+ /// the model that were added by convention.
+ ///
+ /// The name of the entity type to be removed from the model.
+ ///
+ /// The same instance so that additional configuration calls can be chained.
+ ///
+ public virtual ModelBuilder Ignore([NotNull] string name)
+ {
+ Check.NotEmpty(name, nameof(name));
+
+ Builder.Ignore(name, ConfigurationSource.Explicit);
+
+ return this;
+ }
+
///
/// Applies configuration that is defined in an instance.
///
@@ -342,6 +477,43 @@ public virtual OwnedEntityTypeBuilder Owned([NotNull] Type type)
return new OwnedEntityTypeBuilder();
}
+ ///
+ ///
+ /// Marks an entity type as shared type. All references to this type will be configured as separate entity types.
+ ///
+ ///
+ /// Shared type entity type is an entity type which can share CLR type with other types in the model but has
+ /// a unique name and always identified by the name.
+ ///
+ ///
+ /// The entity type to be configured.
+ public virtual SharedEntityTypeBuilder SharedEntity()
+ where T : class
+ {
+ Builder.SharedEntity(typeof(T), ConfigurationSource.Explicit);
+
+ return new SharedEntityTypeBuilder();
+ }
+
+ ///
+ ///
+ /// Marks an entity type as shared type. All references to this type will be configured as separate entity types.
+ ///
+ ///
+ /// Shared type entity type is an entity type which can share CLR type with other types in the model but has
+ /// a unique name and always identified by the name.
+ ///
+ ///
+ /// The entity type to be configured.
+ public virtual SharedEntityTypeBuilder SharedEntity([NotNull] Type type)
+ {
+ Check.NotNull(type, nameof(type));
+
+ Builder.SharedEntity(type, ConfigurationSource.Explicit);
+
+ return new SharedEntityTypeBuilder();
+ }
+
///
/// Configures the default to be used for this model.
/// This strategy indicates how the context detects changes to properties for an instance of an entity type.
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 827ed1689b6..3e41dc691bd 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -2733,12 +2733,20 @@ public static string InvalidSetSharedType([CanBeNull] object typeName)
typeName);
///
- /// Cannot use UsingEntity() passing type '{clrType}' because the model contains shared entity type(s) with same type. Use a type which uniquely defines an entity type.
+ /// Type '{type}' cannot be marked as shared type since entity type with same CLR type exists in the model.
///
- public static string DoNotUseUsingEntityOnSharedClrType([CanBeNull] object clrType)
+ public static string CannotMarkShared([CanBeNull] object type)
=> string.Format(
- GetString("DoNotUseUsingEntityOnSharedClrType", nameof(clrType)),
- clrType);
+ GetString("CannotMarkShared", nameof(type)),
+ type);
+
+ ///
+ /// Type '{type}' is not been configured as shared type in the model. Before calling 'UsingEntity', please mark the type as shared or add the entity type in the model as shared entity.
+ ///
+ public static string TypeNotMarkedAsShared([CanBeNull] object type)
+ => string.Format(
+ GetString("TypeNotMarkedAsShared", nameof(type)),
+ type);
private static string GetString(string name, params string[] formatterNames)
{
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index f4ce86df6a5..55fb9e1342f 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1,17 +1,17 @@
-
@@ -1438,7 +1438,10 @@
Cannot create a DbSet for '{typeName}' because it is configured as an shared type entity type and should be accessed through entity type name based Set method.
-
- Cannot use UsingEntity() passing type '{clrType}' because the model contains shared entity type(s) with same type. Use a type which uniquely defines an entity type.
+
+ Type '{type}' cannot be marked as shared type since entity type with same CLR type exists in the model.
+
+
+ Type '{type}' is not been configured as shared type in the model. Before calling 'UsingEntity', please mark the type as shared or add the entity type in the model as shared entity.
\ No newline at end of file
diff --git a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs
index 1faa3027bdd..eb444c1c398 100644
--- a/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/InternalModelBuilderTest.cs
@@ -477,6 +477,34 @@ public void Cannot_remove_manually_created_association_entity_type(bool removeSk
Assert.Same(manyToManyJoin.Metadata, leftSkipNav.AssociationEntityType);
}
+ [ConditionalFact]
+ public void Can_add_shared_type()
+ {
+ var model = new Model();
+ var modelBuilder = CreateModelBuilder(model);
+
+ var entityBuilder = modelBuilder.Entity(typeof(Customer), ConfigurationSource.Explicit);
+ var sharedTypeName = "SpecialDetails";
+
+ Assert.NotNull(modelBuilder.SharedEntity(sharedTypeName, typeof(Details), ConfigurationSource.Convention));
+
+ Assert.True(model.FindEntityType(sharedTypeName).HasSharedClrType);
+
+ Assert.Equal(
+ CoreStrings.ClashingMismatchedSharedType("SpecialDetails"),
+ Assert.Throws(() => modelBuilder.SharedEntity(sharedTypeName, typeof(Product), ConfigurationSource.DataAnnotation)).Message);
+
+ Assert.NotNull(modelBuilder.Entity(typeof(Product), ConfigurationSource.DataAnnotation));
+
+ Assert.Null(modelBuilder.SharedEntity(typeof(Product).DisplayName(), typeof(Product), ConfigurationSource.DataAnnotation));
+
+ Assert.NotNull(modelBuilder.Entity(typeof(Product), ConfigurationSource.Explicit));
+
+ Assert.Equal(
+ CoreStrings.ClashingNonSharedType(typeof(Product).DisplayName()),
+ Assert.Throws(() => modelBuilder.SharedEntity(typeof(Product).DisplayName(), typeof(Product), ConfigurationSource.Explicit)).Message);
+ }
+
private static void Cleanup(InternalModelBuilder modelBuilder)
{
new ModelCleanupConvention(CreateDependencies())
diff --git a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs
index ad4c4b67d1b..1fe4340310b 100644
--- a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs
+++ b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;
@@ -356,6 +357,107 @@ public virtual void Navigation_properties_can_set_access_mode_using_navigation_n
Assert.Equal(PropertyAccessMode.Field, principal.FindSkipNavigation("Dependents").GetPropertyAccessMode());
Assert.Equal(PropertyAccessMode.Property, dependent.FindSkipNavigation("ManyToManyPrincipals").GetPropertyAccessMode());
}
+
+ [ConditionalFact]
+ public virtual void Can_use_shared_Type_as_join_entity()
+ {
+ var modelBuilder = CreateModelBuilder();
+ var model = modelBuilder.Model;
+
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+
+ modelBuilder.Entity()
+ .HasMany(e => e.Dependents)
+ .WithMany(e => e.ManyToManyPrincipals)
+ .UsingEntity>(
+ "Shared1",
+ e => e.HasOne().WithMany(),
+ e => e.HasOne().WithMany());
+
+ modelBuilder.Entity()
+ .HasMany(e => e.Dependents)
+ .WithMany(e => e.ManyToManyPrincipals)
+ .UsingEntity>(
+ "Shared2",
+ e => e.HasOne().WithMany(),
+ e => e.HasOne().WithMany(),
+ e => e.IndexerProperty("Payload"));
+
+ var shared1 = modelBuilder.Model.FindEntityType("Shared1");
+ Assert.NotNull(shared1);
+ Assert.Equal(2, shared1.GetForeignKeys().Count());
+ Assert.True(shared1.HasSharedClrType);
+ Assert.Equal(typeof(Dictionary), shared1.ClrType);
+
+ var shared2 = modelBuilder.Model.FindEntityType("Shared2");
+ Assert.NotNull(shared2);
+ Assert.Equal(2, shared2.GetForeignKeys().Count());
+ Assert.True(shared2.HasSharedClrType);
+ Assert.Equal(typeof(Dictionary), shared2.ClrType);
+ Assert.NotNull(shared2.FindProperty("Payload"));
+
+ Assert.Equal(
+ CoreStrings.ClashingSharedType(typeof(Dictionary).DisplayName()),
+ Assert.Throws(() => modelBuilder.Entity>()).Message);
+ }
+
+ [ConditionalFact]
+ public virtual void UsingEntity_with_shared_type_fails_when_not_marked()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ Assert.Equal(
+ CoreStrings.TypeNotMarkedAsShared(typeof(ManyToManyJoinWithFields).DisplayName()),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .HasMany(e => e.Dependents)
+ .WithMany(e => e.ManyToManyPrincipals)
+ .UsingEntity(
+ "Shared",
+ r => r.HasOne().WithMany(),
+ l => l.HasOne().WithMany())).Message);
+ }
+
+ [ConditionalFact]
+ public virtual void UsingEntity_with_shared_type_passed_when_marked_as_shared_type()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.SharedEntity();
+
+ var associationEntityType = modelBuilder.Entity()
+ .HasMany(e => e.Dependents)
+ .WithMany(e => e.ManyToManyPrincipals)
+ .UsingEntity(
+ "Shared",
+ r => r.HasOne().WithMany(),
+ l => l.HasOne().WithMany()).Metadata;
+
+ Assert.True(associationEntityType.HasSharedClrType);
+ Assert.Equal("Shared", associationEntityType.Name);
+ Assert.Equal(typeof(ManyToManyJoinWithFields), associationEntityType.ClrType);
+ }
+
+ [ConditionalFact]
+ public virtual void UsingEntity_with_shared_type_passes_when_configured_as_shared()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.SharedEntity("Shared");
+
+ var associationEntityType = modelBuilder.Entity()
+ .HasMany(e => e.Dependents)
+ .WithMany(e => e.ManyToManyPrincipals)
+ .UsingEntity(
+ "Shared",
+ r => r.HasOne().WithMany(),
+ l => l.HasOne().WithMany()).Metadata;
+
+ Assert.True(associationEntityType.HasSharedClrType);
+ Assert.Equal("Shared", associationEntityType.Name);
+ Assert.Equal(typeof(ManyToManyJoinWithFields), associationEntityType.ClrType);
+ }
}
}
}
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs
index a26a57a3d29..9f81aa0d2d7 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs
@@ -48,6 +48,9 @@ public GenericStringTestModelBuilder(TestHelpers testHelpers)
public override TestEntityTypeBuilder Entity()
=> new GenericStringTestEntityTypeBuilder(ModelBuilder.Entity());
+ public override TestEntityTypeBuilder SharedEntity(string name)
+ => new GenericStringTestEntityTypeBuilder(ModelBuilder.SharedEntity(name));
+
public override TestModelBuilder Entity(Action> buildAction)
{
ModelBuilder.Entity(
@@ -56,8 +59,19 @@ public override TestModelBuilder Entity(Action(string name, Action> buildAction)
+ {
+ ModelBuilder.SharedEntity(
+ name,
+ entityTypeBuilder =>
+ buildAction(new GenericStringTestEntityTypeBuilder(entityTypeBuilder)));
+ return this;
+ }
+
public override TestOwnedEntityTypeBuilder Owned()
=> new GenericTestOwnedEntityTypeBuilder(ModelBuilder.Owned());
+ public override TestSharedEntityTypeBuilder SharedEntity()
+ => new GenericTestSharedEntityTypeBuilder(ModelBuilder.SharedEntity());
public override TestModelBuilder Ignore()
{
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs
index d3b08524a5b..37527c5178b 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs
@@ -29,6 +29,9 @@ public GenericTypeTestModelBuilder(TestHelpers testHelpers)
public override TestEntityTypeBuilder Entity()
=> new GenericTypeTestEntityTypeBuilder(ModelBuilder.Entity());
+ public override TestEntityTypeBuilder SharedEntity(string name)
+ => new GenericTypeTestEntityTypeBuilder(ModelBuilder.SharedEntity(name));
+
public override TestModelBuilder Entity(Action> buildAction)
{
ModelBuilder.Entity(
@@ -37,8 +40,19 @@ public override TestModelBuilder Entity(Action(string name, Action> buildAction)
+ {
+ ModelBuilder.SharedEntity(
+ name,
+ entityTypeBuilder =>
+ buildAction(new GenericTypeTestEntityTypeBuilder(entityTypeBuilder)));
+ return this;
+ }
+
public override TestOwnedEntityTypeBuilder Owned()
=> new GenericTestOwnedEntityTypeBuilder(ModelBuilder.Owned());
+ public override TestSharedEntityTypeBuilder SharedEntity()
+ => new GenericTestSharedEntityTypeBuilder(ModelBuilder.SharedEntity());
public override TestModelBuilder Ignore()
{
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
index 67be90429d6..cfde1fa3e94 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
@@ -127,6 +127,11 @@ public GenericTestModelBuilder(TestHelpers testHelpers)
public override TestEntityTypeBuilder Entity()
=> new GenericTestEntityTypeBuilder(ModelBuilder.Entity());
+ public override TestEntityTypeBuilder SharedEntity(string name)
+ => new GenericTestEntityTypeBuilder(ModelBuilder.SharedEntity(name));
+ public override TestSharedEntityTypeBuilder SharedEntity()
+ => new GenericTestSharedEntityTypeBuilder(ModelBuilder.SharedEntity());
+
public override TestModelBuilder Entity(Action> buildAction)
{
ModelBuilder.Entity(
@@ -135,6 +140,15 @@ public override TestModelBuilder Entity(Action(string name, Action> buildAction)
+ {
+ ModelBuilder.SharedEntity(
+ name,
+ entityTypeBuilder =>
+ buildAction(new GenericTestEntityTypeBuilder(entityTypeBuilder)));
+ return this;
+ }
+
public override TestOwnedEntityTypeBuilder Owned()
=> new GenericTestOwnedEntityTypeBuilder(ModelBuilder.Owned());
@@ -349,6 +363,20 @@ public GenericTestOwnedEntityTypeBuilder(OwnedEntityTypeBuilder ownedEn
public OwnedEntityTypeBuilder Instance => OwnedEntityTypeBuilder;
}
+ protected class GenericTestSharedEntityTypeBuilder : TestSharedEntityTypeBuilder,
+ IInfrastructure>
+ where TEntity : class
+ {
+ public GenericTestSharedEntityTypeBuilder(SharedEntityTypeBuilder sharedEntityTypeBuilder)
+ {
+ SharedEntityTypeBuilder = sharedEntityTypeBuilder;
+ }
+
+ protected SharedEntityTypeBuilder SharedEntityTypeBuilder { get; }
+
+ public SharedEntityTypeBuilder Instance => SharedEntityTypeBuilder;
+ }
+
protected class GenericTestPropertyBuilder : TestPropertyBuilder, IInfrastructure>
{
public GenericTestPropertyBuilder(PropertyBuilder propertyBuilder)
@@ -652,7 +680,35 @@ public override TestEntityTypeBuilder UsingEntity ((GenericTestReferenceCollectionBuilder)configureLeft(
new GenericTestEntityTypeBuilder(r))).ReferenceCollectionBuilder));
+ public override TestEntityTypeBuilder UsingEntity(
+ string joinEntityName,
+ Func,
+ TestReferenceCollectionBuilder> configureRight,
+ Func,
+ TestReferenceCollectionBuilder> configureLeft)
+ => new GenericTestEntityTypeBuilder(CollectionCollectionBuilder.UsingEntity(
+ joinEntityName,
+ l => ((GenericTestReferenceCollectionBuilder