Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metadata: Add metadata support for shared entity type #19516

Merged
merged 1 commit into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/EFCore/Extensions/EntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public static IEnumerable<IIndex> GetDeclaredIndexes([NotNull] this IEntityType
=> entityType.AsEntityType().GetDeclaredIndexes();

private static string DisplayNameDefault(this ITypeBase type)
=> type.ClrType != null
=> type.ClrType != null && !type.IsSharedType
? type.ClrType.ShortDisplayName()
: type.Name;

Expand Down Expand Up @@ -345,7 +345,7 @@ public static string DisplayName([NotNull] this ITypeBase type)
[DebuggerStepThrough]
public static string ShortName([NotNull] this ITypeBase type)
{
if (type.ClrType != null)
if (type.ClrType != null && !type.IsSharedType)
{
return type.ClrType.ShortDisplayName();
}
Expand Down
9 changes: 9 additions & 0 deletions src/EFCore/Metadata/IConventionModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ public interface IConventionModel : IModel, IConventionAnnotatable
/// <returns> The new entity type. </returns>
IConventionEntityType AddEntityType([NotNull] Type clrType, bool fromDataAnnotation = false);

/// <summary>
/// Adds an entity type to the model.
/// </summary>
/// <param name="name"> The name of the entity to be added. </param>
/// <param name="clrType"> The CLR class that is used to represent instances of the entity type. </param>
/// <param name="fromDataAnnotation"> Indicates whether the configuration was specified using a data annotation. </param>
/// <returns> The new entity type. </returns>
IConventionEntityType AddEntityType([NotNull] string name, [NotNull] Type clrType, bool fromDataAnnotation = false);

/// <summary>
/// Adds an entity type with a defining navigation to the model.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore/Metadata/IMutableModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ public interface IMutableModel : IModel, IMutableAnnotatable
/// <returns> The new entity type. </returns>
IMutableEntityType AddEntityType([NotNull] Type clrType);

/// <summary>
/// Adds an entity type to the model.
/// </summary>
/// <param name="name"> The name of the entity to be added. </param>
/// <param name="clrType"> The CLR class that is used to represent instances of the entity type. </param>
/// <returns> The new entity type. </returns>
IMutableEntityType AddEntityType([NotNull] string name, [NotNull] Type clrType);

/// <summary>
/// Adds an entity type with a defining navigation to the model.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/EFCore/Metadata/ITypeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,10 @@ public interface ITypeBase : IAnnotatable
/// </para>
/// </summary>
Type ClrType { get; }

/// <summary>
/// Gets whether this entity type can share its ClrType with other entities.
/// </summary>
bool IsSharedType { get; }
}
}
18 changes: 18 additions & 0 deletions src/EFCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,24 @@ public EntityType([NotNull] Type clrType, [NotNull] Model model, ConfigurationSo
Builder = new InternalEntityTypeBuilder(this, model.Builder);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EntityType([NotNull] string name, [NotNull] Type clrType, [NotNull] Model model, ConfigurationSource configurationSource)
: base(name, clrType, model, configurationSource)
{
if (!clrType.IsValidEntityType())
{
throw new ArgumentException(CoreStrings.InvalidEntityType(clrType));
}

_properties = new SortedDictionary<string, Property>(new PropertyNameComparer(this));
smitpatel marked this conversation as resolved.
Show resolved Hide resolved
Builder = new InternalEntityTypeBuilder(this, model.Builder);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
59 changes: 57 additions & 2 deletions src/EFCore/Metadata/Internal/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ private readonly SortedDictionary<string, SortedSet<EntityType>> _entityTypesWit
private readonly Dictionary<string, ConfigurationSource> _ignoredTypeNames
= new Dictionary<string, ConfigurationSource>(StringComparer.Ordinal);

private readonly HashSet<Type> _sharedEntityClrTypes = new HashSet<Type>();

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -146,6 +148,25 @@ public virtual EntityType AddEntityType(
return AddEntityType(entityType);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual EntityType AddEntityType(
[NotNull] string name,
[NotNull] Type type,
ConfigurationSource configurationSource)
{
Check.NotEmpty(name, nameof(name));
Check.NotNull(type, nameof(type));

var entityType = new EntityType(name, type, this, configurationSource);

return AddEntityType(entityType);
}

smitpatel marked this conversation as resolved.
Show resolved Hide resolved
private EntityType AddEntityType(EntityType entityType)
{
var entityTypeName = entityType.Name;
Expand Down Expand Up @@ -193,6 +214,15 @@ private EntityType AddEntityType(EntityType entityType)
throw new InvalidOperationException(CoreStrings.DuplicateEntityType(entityType.DisplayName()));
}

if (entityType.IsSharedType)
{
_sharedEntityClrTypes.Add(entityType.ClrType);
}
else if (_sharedEntityClrTypes.Contains(entityType.ClrType))
{
throw new InvalidOperationException(CoreStrings.ClashingSharedType(entityType.DisplayName()));
}

_entityTypes.Add(entityTypeName, entityType);
}

Expand All @@ -206,7 +236,14 @@ private EntityType AddEntityType(EntityType entityType)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual EntityType FindEntityType([NotNull] Type type)
=> FindEntityType(GetDisplayName(type));
{
if (_sharedEntityClrTypes.Contains(type))
{
throw new InvalidOperationException(CoreStrings.CannotFindEntityWithClrTypeWhenShared(type.DisplayName()));
}

return FindEntityType(GetDisplayName(type));
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -905,6 +942,14 @@ public virtual DebugView DebugView
/// </summary>
IMutableEntityType IMutableModel.AddEntityType(Type type) => AddEntityType(type, ConfigurationSource.Explicit);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
IMutableEntityType IMutableModel.AddEntityType(string name, Type type) => AddEntityType(name, type, ConfigurationSource.Explicit);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -981,7 +1026,8 @@ void IMutableModel.AddIgnored(string name)
/// </summary>
IConventionModelBuilder IConventionModel.Builder
{
[DebuggerStepThrough] get => Builder;
[DebuggerStepThrough]
get => Builder;
}

/// <summary>
Expand Down Expand Up @@ -1020,6 +1066,15 @@ IConventionEntityType IConventionModel.AddEntityType(string name, bool fromDataA
IConventionEntityType IConventionModel.AddEntityType(Type clrType, bool fromDataAnnotation)
=> AddEntityType(clrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
IConventionEntityType IConventionModel.AddEntityType(string name, Type clrType, bool fromDataAnnotation)
=> AddEntityType(name, clrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
28 changes: 28 additions & 0 deletions src/EFCore/Metadata/Internal/TypeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ protected TypeBase([NotNull] string name, [NotNull] Model model, ConfigurationSo
Check.NotNull(model, nameof(model));

Name = name;
IsSharedType = false;
}

/// <summary>
Expand All @@ -59,6 +60,25 @@ protected TypeBase([NotNull] Type clrType, [NotNull] Model model, ConfigurationS

Name = model.GetDisplayName(clrType);
ClrType = clrType;
IsSharedType = false;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected TypeBase([NotNull] string name, [NotNull] Type clrType, [NotNull] Model model, ConfigurationSource configurationSource)
: this(model, configurationSource)
{
Check.NotEmpty(name, nameof(name));
Check.NotNull(clrType, nameof(clrType));
Check.NotNull(model, nameof(model));
smitpatel marked this conversation as resolved.
Show resolved Hide resolved

Name = name;
ClrType = clrType;
IsSharedType = true;
}

private TypeBase([NotNull] Model model, ConfigurationSource configurationSource)
Expand Down Expand Up @@ -91,6 +111,14 @@ private TypeBase([NotNull] Model model, ConfigurationSource configurationSource)
/// </summary>
public virtual string Name { [DebuggerStepThrough] get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual bool IsSharedType { [DebuggerStepThrough] get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
16 changes: 16 additions & 0 deletions src/EFCore/Properties/CoreStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/EFCore/Properties/CoreStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1260,4 +1260,10 @@
<data name="BackingFieldOnIndexer" xml:space="preserve">
<value>Cannot set backing field '{field}' for the indexer property '{entityType}.{property}'. Indexer properties are not allowed to use a backing field.</value>
</data>
<data name="ClashingSharedType" xml:space="preserve">
<value>The entity type '{entityType}' cannot be added to the model because a shared entity type with the same clr type already exists.</value>
</data>
<data name="CannotFindEntityWithClrTypeWhenShared" xml:space="preserve">
<value>Cannot find entity type with type '{clrType}' since model contains shared entity type(s) with same type.</value>
</data>
</root>
5 changes: 5 additions & 0 deletions test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ private class FakeEntityType : IEntityType
public IEnumerable<IAnnotation> GetAnnotations() => throw new NotImplementedException();
public IModel Model { get; }
public string Name { get; }
public bool IsSharedType { get; }
public Type ClrType { get; }
public IEntityType BaseType { get; }
public string DefiningNavigationName { get; }
Expand Down Expand Up @@ -102,6 +103,10 @@ public void Display_name_is_entity_type_name_when_no_CLR_type()
"Everything.Is+Awesome<When.We, re.Living<Our.Dream>>",
CreateModel().AddEntityType("Everything.Is+Awesome<When.We, re.Living<Our.Dream>>").DisplayName());

[ConditionalFact]
public void Display_name_is_entity_type_name_when_shared_entity_type()
=> Assert.Equal("PostTag", CreateModel().AddEntityType("PostTag", typeof(Dictionary<string, object>)).DisplayName());

[ConditionalFact]
public void Name_is_prettified_CLR_full_name()
{
Expand Down
33 changes: 32 additions & 1 deletion test/EFCore.Tests/Metadata/Internal/ModelTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,41 @@ public void Can_add_and_remove_entity_by_name()
Assert.Null(((EntityType)entityType).Builder);
}

[ConditionalFact]
public void Can_add_and_remove_shared_entity()
{
var model = CreateModel();
var entityTypeName = "SharedCustomer1";
Assert.Null(model.FindEntityType(typeof(Customer)));
Assert.Null(model.FindEntityType(entityTypeName));

var entityType = model.AddEntityType(entityTypeName, typeof(Customer));

Assert.Equal(typeof(Customer), entityType.ClrType);
Assert.Equal(entityTypeName, entityType.Name);
Assert.NotNull(model.FindEntityType(entityTypeName));
Assert.Same(model, entityType.Model);
Assert.NotNull(((EntityType)entityType).Builder);

Assert.Same(entityType, model.FindEntityType(entityTypeName));
Assert.Equal(
CoreStrings.CannotFindEntityWithClrTypeWhenShared(typeof(Customer).DisplayName()),
Assert.Throws<InvalidOperationException>(
() => model.FindEntityType(typeof(Customer))).Message);

Assert.Equal(new[] { entityType }, model.GetEntityTypes().ToArray());

Assert.Same(entityType, model.RemoveEntityType(entityType.Name));

Assert.Null(model.RemoveEntityType(entityType.Name));
Assert.Null(model.FindEntityType(entityTypeName));
Assert.Null(((EntityType)entityType).Builder);
}
smitpatel marked this conversation as resolved.
Show resolved Hide resolved

[ConditionalFact]
public void Can_add_weak_entity_types()
{
IMutableModel model = CreateModel();
var model = CreateModel();
var customerType = model.AddEntityType(typeof(Customer));
var idProperty = customerType.AddProperty(Customer.IdProperty);
var customerKey = customerType.AddKey(idProperty);
Expand Down