Skip to content

Commit

Permalink
Metadata: Add metadata support for shared type entity type
Browse files Browse the repository at this point in the history
Part of #9914
  • Loading branch information
smitpatel committed Jan 8, 2020
1 parent e1572b5 commit d19fcb1
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 5 deletions.
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));
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);
}

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));

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);
}

[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

0 comments on commit d19fcb1

Please sign in to comment.