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

Store null table name and TPH mapping strategy in the snapshot #28359

Merged
merged 1 commit into from
Jul 7, 2022
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
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