diff --git a/src/EFCore/Extensions/MutableModelExtensions.cs b/src/EFCore/Extensions/MutableModelExtensions.cs
index 2f77272b42b..e9b85b64073 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 owned type to.
+ /// The type of the entity type that should be owned.
+ public static void 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/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..31b670443b8 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,33 @@ 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())))
+ {
+ Metadata.RemoveEntityType(entityType);
+ }
+
+ 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 +628,8 @@ public virtual bool CanSetPropertyAccessMode(
///
IConventionModel IConventionModelBuilder.Metadata
{
- [DebuggerStepThrough] get => Metadata;
+ [DebuggerStepThrough]
+ get => Metadata;
}
///
@@ -573,6 +642,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..94f2c17aabb 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,16 @@ 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 (_sharedEntityClrTypes.ContainsKey(entityType.ClrType))
{
throw new InvalidOperationException(CoreStrings.ClashingSharedType(entityType.DisplayName()));
}
@@ -772,7 +779,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 +884,29 @@ 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 void 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);
+ }
+ }
+
///
/// 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..8bd026b8767 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -2733,12 +2733,12 @@ 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);
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..f1eaf7f6971 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -1,17 +1,17 @@
-
@@ -1438,7 +1438,7 @@
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.
\ 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..788c2d2f72c 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,71 @@ 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 Cannot_add_shared_type_when_non_shared_exists()
+ {
+ var modelBuilder = CreateModelBuilder();
+
+ modelBuilder.Entity();
+
+ Assert.Equal(
+ CoreStrings.ClashingNonSharedType("Shared"),
+ Assert.Throws(
+ () => modelBuilder.Entity()
+ .HasMany(e => e.Dependents)
+ .WithMany(e => e.ManyToManyPrincipals)
+ .UsingEntity(
+ "Shared",
+ r => r.HasOne().WithMany(),
+ l => l.HasOne().WithMany())).Message);
+ }
+
+
}
}
}
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipStringTest.cs
index a26a57a3d29..2c06d56ae6c 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,6 +59,15 @@ 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());
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericRelationshipTypeTest.cs
index d3b08524a5b..7748e546236 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,6 +40,15 @@ 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());
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
index 67be90429d6..fc3e31d4343 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
@@ -127,6 +127,9 @@ 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 TestModelBuilder Entity(Action> buildAction)
{
ModelBuilder.Entity(
@@ -135,6 +138,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());
@@ -652,7 +664,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)configureRight(
+ new GenericTestEntityTypeBuilder(l))).ReferenceCollectionBuilder,
+ r => ((GenericTestReferenceCollectionBuilder)configureLeft(
+ new GenericTestEntityTypeBuilder(r))).ReferenceCollectionBuilder));
+
+ public override TestEntityTypeBuilder UsingEntity(
+ Func,
+ TestReferenceCollectionBuilder> configureRight,
+ Func,
+ TestReferenceCollectionBuilder> configureLeft,
+ Action> configureAssociation)
+ where TAssociationEntity : class
+ => new GenericTestEntityTypeBuilder(CollectionCollectionBuilder.UsingEntity(
+ l => ((GenericTestReferenceCollectionBuilder)configureRight(
+ new GenericTestEntityTypeBuilder(l))).ReferenceCollectionBuilder,
+ r => ((GenericTestReferenceCollectionBuilder)configureLeft(
+ new GenericTestEntityTypeBuilder(r))).ReferenceCollectionBuilder,
+ e => configureAssociation(new GenericTestEntityTypeBuilder(e))));
+
public override TestEntityTypeBuilder UsingEntity(
+ string joinEntityName,
Func,
TestReferenceCollectionBuilder> configureRight,
Func,
@@ -660,6 +700,7 @@ public override TestEntityTypeBuilder UsingEntity> configureAssociation)
where TAssociationEntity : class
=> new GenericTestEntityTypeBuilder(CollectionCollectionBuilder.UsingEntity(
+ joinEntityName,
l => ((GenericTestReferenceCollectionBuilder)configureRight(
new GenericTestEntityTypeBuilder(l))).ReferenceCollectionBuilder,
r => ((GenericTestReferenceCollectionBuilder)configureLeft(
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs
index 4c491d10c51..d861fb921bd 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericStringTest.cs
@@ -81,6 +81,9 @@ public NonGenericStringTestModelBuilder(TestHelpers testHelpers)
public override TestEntityTypeBuilder Entity()
=> new NonGenericStringTestEntityTypeBuilder(ModelBuilder.Entity(typeof(TEntity)));
+ public override TestEntityTypeBuilder SharedEntity(string name)
+ => new NonGenericStringTestEntityTypeBuilder(ModelBuilder.SharedEntity(name, typeof(TEntity)));
+
public override TestModelBuilder Entity(Action> buildAction)
{
ModelBuilder.Entity(
@@ -89,6 +92,15 @@ public override TestModelBuilder Entity(Action(string name, Action> buildAction)
+ {
+ ModelBuilder.SharedEntity(
+ name,
+ typeof(TEntity),
+ e => buildAction(new NonGenericStringTestEntityTypeBuilder(e)));
+ return this;
+ }
+
public override TestOwnedEntityTypeBuilder Owned()
=> new NonGenericTestOwnedEntityTypeBuilder(ModelBuilder.Owned(typeof(TEntity)));
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
index c01283d2765..a1fb696af1b 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
@@ -75,6 +75,9 @@ public NonGenericTestModelBuilder(TestHelpers testHelpers)
public override TestEntityTypeBuilder Entity()
=> new NonGenericTestEntityTypeBuilder(ModelBuilder.Entity(typeof(TEntity)));
+ public override TestEntityTypeBuilder SharedEntity(string name)
+ => new NonGenericTestEntityTypeBuilder(ModelBuilder.SharedEntity(name, typeof(TEntity)));
+
public override TestModelBuilder Entity(Action> buildAction)
{
ModelBuilder.Entity(
@@ -83,6 +86,15 @@ public override TestModelBuilder Entity(Action(string name, Action> buildAction)
+ {
+ ModelBuilder.SharedEntity(
+ name,
+ typeof(TEntity), entityTypeBuilder =>
+ buildAction(new NonGenericTestEntityTypeBuilder(entityTypeBuilder)));
+ return this;
+ }
+
public override TestOwnedEntityTypeBuilder Owned()
=> new NonGenericTestOwnedEntityTypeBuilder(ModelBuilder.Owned(typeof(TEntity)));
@@ -646,7 +658,37 @@ public override TestEntityTypeBuilder UsingEntity ((NonGenericTestReferenceCollectionBuilder)configureLeft(
new NonGenericTestEntityTypeBuilder(r))).ReferenceCollectionBuilder));
+ public override TestEntityTypeBuilder UsingEntity(
+ string joinEntityName,
+ Func,
+ TestReferenceCollectionBuilder> configureRight,
+ Func,
+ TestReferenceCollectionBuilder> configureLeft)
+ => new NonGenericTestEntityTypeBuilder(CollectionCollectionBuilder.UsingEntity(
+ joinEntityName,
+ typeof(TAssociationEntity),
+ l => ((NonGenericTestReferenceCollectionBuilder