Skip to content

Commit

Permalink
Store null table name and TPH mapping strategy in the snapshot
Browse files Browse the repository at this point in the history
Use DbSet names for TPC and TPT entity types

Fixes #28193
Fixes #29298
  • Loading branch information
AndriySvyryd committed Jul 2, 2022
1 parent e26e7cf commit efd1781
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -845,8 +845,8 @@ private void GenerateTableMapping(
var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
var tableName = (string?)tableNameAnnotation?.Value ?? table?.Name;
if (tableNameAnnotation == null
&& (tableName == null
|| entityType.BaseType?.GetTableName() == tableName))
&& entityType.BaseType != null
&& entityType.BaseType.GetTableName() == tableName)
{
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static partial class RelationalEntityTypeBuilderExtensions
/// Configures TPC as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// All properties will be mapped to columns on the corresponding object.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-inheritance">Entity type hierarchy mapping</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static EntityTypeBuilder UseTpcMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
Expand All @@ -32,6 +35,9 @@ public static EntityTypeBuilder UseTpcMappingStrategy(this EntityTypeBuilder ent
/// Configures TPH as the mapping strategy for the derived types. All types will be mapped to the same database object.
/// This is the default mapping strategy.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-inheritance">Entity type hierarchy mapping</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static EntityTypeBuilder UseTphMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
Expand All @@ -45,6 +51,9 @@ public static EntityTypeBuilder UseTphMappingStrategy(this EntityTypeBuilder ent
/// Configures TPT as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// Only the declared properties will be mapped to columns on the corresponding object.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-inheritance">Entity type hierarchy mapping</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder entityTypeBuilder)
Expand All @@ -58,6 +67,9 @@ public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder ent
/// Configures TPC as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// All properties will be mapped to columns on the corresponding object.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-inheritance">Entity type hierarchy mapping</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static EntityTypeBuilder<TEntity> UseTpcMappingStrategy<TEntity>(this EntityTypeBuilder<TEntity> entityTypeBuilder)
Expand All @@ -68,6 +80,9 @@ public static EntityTypeBuilder<TEntity> UseTpcMappingStrategy<TEntity>(this Ent
/// Configures TPH as the mapping strategy for the derived types. All types will be mapped to the same database object.
/// This is the default mapping strategy.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-inheritance">Entity type hierarchy mapping</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static EntityTypeBuilder<TEntity> UseTphMappingStrategy<TEntity>(this EntityTypeBuilder<TEntity> entityTypeBuilder)
Expand All @@ -78,12 +93,60 @@ public static EntityTypeBuilder<TEntity> UseTphMappingStrategy<TEntity>(this Ent
/// Configures TPT as the mapping strategy for the derived types. Each type will be mapped to a different database object.
/// Only the declared properties will be mapped to columns on the corresponding object.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-inheritance">Entity type hierarchy mapping</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
public static EntityTypeBuilder<TEntity> UseTptMappingStrategy<TEntity>(this EntityTypeBuilder<TEntity> entityTypeBuilder)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)((EntityTypeBuilder)entityTypeBuilder).UseTptMappingStrategy();

/// <summary>
/// Sets the hierarchy mapping strategy.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-inheritance">Entity type hierarchy mapping</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <param name="strategy">The mapping strategy for the derived types.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns>
/// The same builder instance if the configuration was applied,
/// <see langword="null" /> otherwise.
/// </returns>
public static IConventionEntityTypeBuilder? UseMappingStrategy(
this IConventionEntityTypeBuilder entityTypeBuilder,
string? strategy,
bool fromDataAnnotation = false)
{
if (!entityTypeBuilder.CanSetMappingStrategy(strategy, fromDataAnnotation))
{
return null;
}

entityTypeBuilder.Metadata.SetMappingStrategy(strategy, fromDataAnnotation);
return entityTypeBuilder;
}

/// <summary>
/// Returns a value indicating whether the hierarchy mapping strategy can be configured
/// using the specified configuration source.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information and examples.
/// </remarks>
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
/// <param name="strategy">The mapping strategy for the derived types.</param>
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
/// <returns><see langword="true" /> if the configuration can be applied.</returns>
public static bool CanSetMappingStrategy(
this IConventionEntityTypeBuilder entityTypeBuilder,
string? strategy,
bool fromDataAnnotation = false)
=> entityTypeBuilder.CanSetAnnotation
(RelationalAnnotationNames.MappingStrategy, strategy, fromDataAnnotation);

/// <summary>
/// Mark the table that this entity type is mapped to as excluded from migrations.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1451,7 +1451,7 @@ public static IEnumerable<IReadOnlyRelationalPropertyOverrides> GetOverrides(thi
/// <param name="property">The property.</param>
/// <returns>The property facet overrides.</returns>
public static IEnumerable<IMutableRelationalPropertyOverrides> GetOverrides(this IMutableProperty property)
=> ((IEnumerable<IMutableRelationalPropertyOverrides>?)RelationalPropertyOverrides.Get(property))
=> RelationalPropertyOverrides.Get(property)?.Cast<IMutableRelationalPropertyOverrides>()
?? Enumerable.Empty<IMutableRelationalPropertyOverrides>();

/// <summary>
Expand All @@ -1466,7 +1466,7 @@ public static IEnumerable<IMutableRelationalPropertyOverrides> GetOverrides(this
/// <param name="property">The property.</param>
/// <returns>The property facet overrides.</returns>
public static IEnumerable<IConventionRelationalPropertyOverrides> GetOverrides(this IConventionProperty property)
=> ((IEnumerable<IConventionRelationalPropertyOverrides>?)RelationalPropertyOverrides.Get(property))
=> RelationalPropertyOverrides.Get(property)?.Cast<IConventionRelationalPropertyOverrides>()
?? Enumerable.Empty<IConventionRelationalPropertyOverrides>();

/// <summary>
Expand All @@ -1481,7 +1481,7 @@ public static IEnumerable<IConventionRelationalPropertyOverrides> GetOverrides(t
/// <param name="property">The property.</param>
/// <returns>The property facet overrides.</returns>
public static IEnumerable<IRelationalPropertyOverrides> GetOverrides(this IProperty property)
=> ((IEnumerable<IRelationalPropertyOverrides>?)RelationalPropertyOverrides.Get(property))
=> RelationalPropertyOverrides.Get(property)?.Cast<IRelationalPropertyOverrides>()
?? Enumerable.Empty<IRelationalPropertyOverrides>();

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
IConventionContext<IConventionModelBuilder> context)
{
var allRoots = new HashSet<IConventionEntityType>();
var nonTphRoots = new HashSet<IConventionEntityType>();

foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
Expand All @@ -49,15 +50,24 @@ public virtual void ProcessModelFinalizing(
continue;
}

var root = entityType.GetRootType();
allRoots.Add(root);
var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy];
if (mappingStrategy == null)
{
mappingStrategy = (string?)entityType.GetRootType()[RelationalAnnotationNames.MappingStrategy];
mappingStrategy = (string?)root[RelationalAnnotationNames.MappingStrategy];
if (mappingStrategy == null
&& root.GetDiscriminatorPropertyConfigurationSource() == ConfigurationSource.Explicit)
{
mappingStrategy = RelationalAnnotationNames.TphMappingStrategy;
root.Builder.UseMappingStrategy(RelationalAnnotationNames.TphMappingStrategy);
continue;
}
}

if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy)
{
nonTphRoots.Add(entityType.GetRootType());
nonTphRoots.Add(root);
continue;
}

Expand All @@ -70,6 +80,7 @@ public virtual void ProcessModelFinalizing(
|| entityType.GetSchema() != entityType.BaseType.GetSchema())
{
mappingStrategy = RelationalAnnotationNames.TptMappingStrategy;
root.Builder.UseMappingStrategy(mappingStrategy);
}
}

Expand All @@ -96,7 +107,7 @@ public virtual void ProcessModelFinalizing(
}
}

nonTphRoots.Add(entityType.GetRootType());
nonTphRoots.Add(root);
continue;
}
}
Expand All @@ -106,13 +117,20 @@ public virtual void ProcessModelFinalizing(
&& (viewName != entityType.BaseType.GetViewName()
|| entityType.GetViewSchema() != entityType.BaseType.GetViewSchema()))
{
nonTphRoots.Add(entityType.GetRootType());
nonTphRoots.Add(root);
continue;
}
}

foreach (var root in nonTphRoots)
{
allRoots.Remove(root);
root.Builder.HasNoDiscriminator();
}

foreach (var root in allRoots)
{
root.Builder.UseMappingStrategy(RelationalAnnotationNames.TphMappingStrategy);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public override ConventionSet CreateConventionSet()
conventionSet.EntityTypeBaseTypeChangedConventions.Add(checkConstraintConvention);
conventionSet.EntityTypeBaseTypeChangedConventions.Add(triggerConvention);

conventionSet.EntityTypeAnnotationChangedConventions.Add(tableNameFromDbSetConvention);

ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, valueGenerationConvention);

ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, valueGenerationConvention);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
public class TableNameFromDbSetConvention :
IEntityTypeAddedConvention,
IEntityTypeBaseTypeChangedConvention,
IEntityTypeAnnotationChangedConvention,
IModelFinalizingConvention
{
private readonly IDictionary<Type, string> _sets;
Expand Down Expand Up @@ -63,13 +64,7 @@ public TableNameFromDbSetConvention(
/// </summary>
protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }

/// <summary>
/// Called after the base type of an entity type changes.
/// </summary>
/// <param name="entityTypeBuilder">The builder for the entity type.</param>
/// <param name="newBaseType">The new base entity type.</param>
/// <param name="oldBaseType">The old base entity type.</param>
/// <param name="context">Additional information associated with convention execution.</param>
/// <inheritdoc />
public virtual void ProcessEntityTypeBaseTypeChanged(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionEntityType? newBaseType,
Expand All @@ -79,7 +74,9 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
var entityType = entityTypeBuilder.Metadata;

if (oldBaseType == null
&& newBaseType != null)
&& newBaseType != null
&& (entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
== RelationalAnnotationNames.TphMappingStrategy)
{
entityTypeBuilder.HasNoAnnotation(RelationalAnnotationNames.TableName);
}
Expand All @@ -92,24 +89,46 @@ public virtual void ProcessEntityTypeBaseTypeChanged(
}
}

/// <summary>
/// Called after an entity type is added to the model.
/// </summary>
/// <param name="entityTypeBuilder">The builder for the entity type.</param>
/// <param name="context">Additional information associated with convention execution.</param>
/// <inheritdoc />
public virtual void ProcessEntityTypeAdded(
IConventionEntityTypeBuilder entityTypeBuilder,
IConventionContext<IConventionEntityTypeBuilder> context)
{
var entityType = entityTypeBuilder.Metadata;
if (entityType.BaseType == null
&& !entityType.HasSharedClrType
if (!entityType.HasSharedClrType
&& (entityType.BaseType == null
|| (entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
!= RelationalAnnotationNames.TphMappingStrategy)
&& _sets.TryGetValue(entityType.ClrType, out var setName))
{
entityTypeBuilder.ToTable(setName);
}
}

/// <inheritdoc />
public virtual void ProcessEntityTypeAnnotationChanged(
IConventionEntityTypeBuilder entityTypeBuilder,
string name,
IConventionAnnotation? annotation,
IConventionAnnotation? oldAnnotation,
IConventionContext<IConventionAnnotation> context)
{
if (name == RelationalAnnotationNames.MappingStrategy
&& annotation != null
&& (entityTypeBuilder.Metadata.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy)
!= RelationalAnnotationNames.TphMappingStrategy)
{
foreach (var deriverEntityType in entityTypeBuilder.Metadata.GetDerivedTypesInclusive())
{
if (!deriverEntityType.HasSharedClrType
&& _sets.TryGetValue(deriverEntityType.ClrType, out var setName))
{
deriverEntityType.Builder.ToTable(setName);
}
}
}
}

/// <inheritdoc />
public virtual void ProcessModelFinalizing(
IConventionModelBuilder modelBuilder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class CSharpMigrationsGeneratorTest
{
private static readonly string _nl = Environment.NewLine;
private static readonly string _toTable = _nl + @"entityTypeBuilder.ToTable(""WithAnnotations"")";
private static readonly string _toNullTable = _nl + @"entityTypeBuilder.ToTable((string)null)";

[ConditionalFact]
public void Test_new_annotations_handled_for_entity_types()
Expand Down Expand Up @@ -139,19 +140,22 @@ public void Test_new_annotations_handled_for_entity_types()
#pragma warning disable CS0612 // Type or member is obsolete
CoreAnnotationNames.DefiningQuery,
#pragma warning restore CS0612 // Type or member is obsolete
(Expression.Lambda(Expression.Constant(null)), "")
(Expression.Lambda(Expression.Constant(null)), _toNullTable)
},
{
RelationalAnnotationNames.ViewName,
("MyView", _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToView) + @"(""MyView"")")
("MyView", _toNullTable + ";" + _nl + _nl
+ "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToView) + @"(""MyView"")")
},
{
RelationalAnnotationNames.FunctionName,
(null, _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToFunction) + @"(null)")
(null, _toNullTable + ";" + _nl + _nl
+ "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToFunction) + @"(null)")
},
{
RelationalAnnotationNames.SqlQuery,
(null, _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToSqlQuery) + @"(null)")
(null, _toNullTable + ";" + _nl + _nl
+ "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToSqlQuery) + @"(null)")
}
};

Expand Down
Loading

0 comments on commit efd1781

Please sign in to comment.