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

Add metadata support for TPC #27628

Merged
merged 1 commit into from
Mar 15, 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 @@ -34,7 +34,9 @@ public SnapshotModelProcessor(
typeof(RelationalAnnotationNames)
.GetRuntimeFields()
.Where(p => p.Name != nameof(RelationalAnnotationNames.Prefix))
.Select(p => ((string)p.GetValue(null)!)[(RelationalAnnotationNames.Prefix.Length - 1)..]));
.Select(p => (string)p.GetValue(null)!)
.Where(v => v.IndexOf(':') > 0)
.Select(v => v[(RelationalAnnotationNames.Prefix.Length - 1)..]));
_modelRuntimeInitializer = modelRuntimeInitializer;
}

Expand Down
30 changes: 30 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ private enum Id
ForeignKeyPropertiesMappedToUnrelatedTables,
OptionalDependentWithoutIdentifyingPropertyWarning,
DuplicateColumnOrders,
ForeignKeyTPCPrincipalWarning,
TpcStoreGeneratedIdentityWarning,

// Update events
BatchReadyForExecution = CoreEventId.RelationalBaseId + 700,
Expand Down Expand Up @@ -739,6 +741,34 @@ private static EventId MakeValidationId(Id id)
public static readonly EventId ForeignKeyPropertiesMappedToUnrelatedTables =
MakeValidationId(Id.ForeignKeyPropertiesMappedToUnrelatedTables);

/// <summary>
/// A foreign key specifies properties which don't map to the related tables.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Model.Validation" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="ForeignKeyEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId ForeignKeyTPCPrincipalWarning =
MakeValidationId(Id.ForeignKeyTPCPrincipalWarning);

/// <summary>
/// The PK is using store-generated values in TPC.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Model.Validation" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="PropertyEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId TpcStoreGeneratedIdentityWarning =
MakeValidationId(Id.TpcStoreGeneratedIdentityWarning);

/// <summary>
/// The entity does not have any property with a non-default value to identify whether the entity exists.
/// </summary>
Expand Down
96 changes: 96 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2831,6 +2831,102 @@ private static string ForeignKeyPropertiesMappedToUnrelatedTables(EventDefinitio
p.ForeignKey.PrincipalEntityType.GetSchemaQualifiedTableName()));
}

/// <summary>
/// Logs the <see cref="RelationalEventId.ForeignKeyTPCPrincipalWarning" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
/// <param name="foreignKey">The foreign key.</param>
public static void ForeignKeyTPCPrincipalWarning(
this IDiagnosticsLogger<DbLoggerCategory.Model.Validation> diagnostics,
IForeignKey foreignKey)
{
var definition = RelationalResources.LogForeignKeyTPCPrincipal(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(
diagnostics,
l => l.Log(
definition.Level,
definition.EventId,
definition.MessageFormat,
foreignKey.Properties.Format(),
foreignKey.DeclaringEntityType.DisplayName(),
foreignKey.PrincipalEntityType.DisplayName(),
foreignKey.PrincipalEntityType.DisplayName(),
foreignKey.PrincipalEntityType.GetSchemaQualifiedTableName()!,
foreignKey.DeclaringEntityType.DisplayName(),
foreignKey.PrincipalEntityType.DisplayName()));
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new ForeignKeyEventData(
definition,
ForeignKeyTPCPrincipal,
foreignKey);

diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}

private static string ForeignKeyTPCPrincipal(EventDefinitionBase definition, EventData payload)
{
var d = (FallbackEventDefinition)definition;
var p = (ForeignKeyEventData)payload;
return d.GenerateMessage(
l => l.Log(
d.Level,
d.EventId,
d.MessageFormat,
p.ForeignKey.Properties.Format(),
p.ForeignKey.DeclaringEntityType.DisplayName(),
p.ForeignKey.PrincipalEntityType.DisplayName(),
p.ForeignKey.PrincipalEntityType.GetSchemaQualifiedTableName()!,
p.ForeignKey.PrincipalEntityType.DisplayName(),
p.ForeignKey.DeclaringEntityType.DisplayName(),
p.ForeignKey.PrincipalEntityType.DisplayName()));
}

/// <summary>
/// Logs the <see cref="RelationalEventId.TpcStoreGeneratedIdentityWarning" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
/// <param name="property">The entity type on which the index is defined.</param>
public static void TpcStoreGeneratedIdentityWarning(
this IDiagnosticsLogger<DbLoggerCategory.Model.Validation> diagnostics,
IProperty property)
{
var definition = RelationalResources.LogTpcStoreGeneratedIdentity(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(
diagnostics,
property.Name,
property.DeclaringEntityType.DisplayName());
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new PropertyEventData(
definition,
TpcStoreGeneratedIdentity,
property);

diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}

private static string TpcStoreGeneratedIdentity(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string, string>)definition;
var p = (PropertyEventData)payload;
return d.GenerateMessage(
p.Property.Name,
p.Property.DeclaringEntityType.DisplayName());
}

/// <summary>
/// Logs the <see cref="RelationalEventId.OptionalDependentWithoutIdentifyingPropertyWarning" /> event.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,24 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[EntityFrameworkInternal]
public EventDefinitionBase? LogForeignKeyPropertiesMappedToUnrelatedTables;

/// <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>
[EntityFrameworkInternal]
public EventDefinitionBase? LogForeignKeyTPCPrincipal;
AndriySvyryd marked this conversation as resolved.
Show resolved Hide resolved

/// <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>
[EntityFrameworkInternal]
public EventDefinitionBase? LogTpcStoreGeneratedIdentity;

/// <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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,79 @@ namespace Microsoft.EntityFrameworkCore;
/// </remarks>
public static class RelationalEntityTypeBuilderExtensions
{
/// <summary>
/// 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>
/// <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)
{
entityTypeBuilder.Metadata.SetMappingStrategy(RelationalAnnotationNames.TpcMappingStrategy);

return entityTypeBuilder;
}

/// <summary>
/// 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>
/// <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)
{
entityTypeBuilder.Metadata.SetMappingStrategy(RelationalAnnotationNames.TphMappingStrategy);

return entityTypeBuilder;
}

/// <summary>
/// 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>
/// <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)
{
entityTypeBuilder.Metadata.SetMappingStrategy(RelationalAnnotationNames.TptMappingStrategy);

return entityTypeBuilder;
}

/// <summary>
/// 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>
/// <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)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)((EntityTypeBuilder)entityTypeBuilder).UseTpcMappingStrategy();

/// <summary>
/// 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>
/// <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)
where TEntity : class
=> (EntityTypeBuilder<TEntity>)((EntityTypeBuilder)entityTypeBuilder).UseTphMappingStrategy();

/// <summary>
/// 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-migrations">Database migrations</see> for more information and examples.
/// </remarks>
/// <typeparam name="TEntity">The entity type being configured.</typeparam>
/// <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>
/// Configures the table that the entity type maps to when targeting a relational database.
/// </summary>
Expand Down
92 changes: 77 additions & 15 deletions src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,12 @@ public static class RelationalEntityTypeExtensions
return (string?)nameAnnotation.Value;
}

if (entityType.BaseType != null)
{
return entityType.GetRootType().GetTableName();
}

return ((entityType as IConventionEntityType)?.GetViewNameConfigurationSource() == null)
&& ((entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null)
&& (entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null
#pragma warning disable CS0618 // Type or member is obsolete
&& ((entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null)
&& (entityType as IConventionEntityType)?.GetDefiningQueryConfigurationSource() == null
#pragma warning restore CS0618 // Type or member is obsolete
&& ((entityType as IConventionEntityType)?.GetSqlQueryConfigurationSource() == null)
&& (entityType as IConventionEntityType)?.GetSqlQueryConfigurationSource() == null
? GetDefaultTableName(entityType)
: null;
}
Expand All @@ -57,6 +52,12 @@ public static class RelationalEntityTypeExtensions
/// <returns>The default name of the table to which the entity type would be mapped.</returns>
public static string? GetDefaultTableName(this IReadOnlyEntityType entityType, bool truncate = true)
{
if (entityType.GetDiscriminatorPropertyName() != null
&& entityType.BaseType != null)
{
return entityType.GetRootType().GetTableName();
}

var ownership = entityType.FindOwnership();
if (ownership != null
&& ownership.IsUnique)
Expand All @@ -77,6 +78,12 @@ public static class RelationalEntityTypeExtensions
: $"{ownership.PrincipalToDependent.Name}_{name}";
}

if (entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy
&& !entityType.ClrType.IsInstantiable())
{
return null;
}

return truncate
? Uniquifier.Truncate(name, entityType.Model.GetMaxIdentifierLength())
: name;
Expand Down Expand Up @@ -273,15 +280,10 @@ public static IEnumerable<ITableMapping> GetTableMappings(this IEntityType entit
/// <returns>The name of the view to which the entity type is mapped.</returns>
public static string? GetViewName(this IReadOnlyEntityType entityType)
{
var nameAnnotation = (string?)entityType[RelationalAnnotationNames.ViewName];
var nameAnnotation = entityType.FindAnnotation(RelationalAnnotationNames.ViewName);
if (nameAnnotation != null)
{
return nameAnnotation;
}

if (entityType.BaseType != null)
{
return entityType.GetRootType().GetViewName();
return (string?)nameAnnotation.Value;
}

return ((entityType as IConventionEntityType)?.GetFunctionNameConfigurationSource() == null)
Expand All @@ -300,6 +302,12 @@ public static IEnumerable<ITableMapping> GetTableMappings(this IEntityType entit
/// <returns>The default name of the table to which the entity type would be mapped.</returns>
public static string? GetDefaultViewName(this IReadOnlyEntityType entityType)
{
if (entityType.GetDiscriminatorPropertyName() != null
&& entityType.BaseType != null)
{
return entityType.GetRootType().GetViewName();
}

var ownership = entityType.FindOwnership();
return ownership != null
&& ownership.IsUnique
Expand Down Expand Up @@ -957,4 +965,58 @@ public static void SetIsTableExcludedFromMigrations(this IMutableEntityType enti
this IConventionEntityType entityType)
=> entityType.FindAnnotation(RelationalAnnotationNames.IsTableExcludedFromMigrations)
?.GetConfigurationSource();

/// <summary>
/// Gets the mapping strategy for the derived types.
/// </summary>
/// <param name="entityType">The entity type.</param>
/// <returns>The mapping strategy for the derived types.</returns>
public static string? GetMappingStrategy(this IReadOnlyEntityType entityType)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a mapping strategy be applied to the entire model rather than to each entity type?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet. #27642

{
var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy];
if (mappingStrategy != null)
{
return mappingStrategy;
}

if (entityType.BaseType != null)
{
return entityType.GetRootType().GetMappingStrategy();
}

return null;
}

/// <summary>
/// Sets the mapping strategy for the derived types.
/// </summary>
/// <param name="entityType">The entity type.</param>
/// <param name="strategy">The mapping strategy for the derived types.</param>
public static void SetMappingStrategy(this IMutableEntityType entityType, string? strategy)
=> entityType.SetOrRemoveAnnotation(RelationalAnnotationNames.MappingStrategy, strategy);

/// <summary>
/// Sets the mapping strategy for the derived types.
/// </summary>
/// <param name="entityType">The entity type.</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 configured value.</returns>
public static string? SetMappingStrategy(
this IConventionEntityType entityType,
string? strategy,
bool fromDataAnnotation = false)
=> (string?)entityType.SetOrRemoveAnnotation(
RelationalAnnotationNames.MappingStrategy, strategy, fromDataAnnotation)
?.Value;

/// <summary>
/// Gets the <see cref="ConfigurationSource" /> for <see cref="GetMappingStrategy" />.
/// </summary>
/// <param name="entityType">The entity type to find configuration source for.</param>
/// <returns>The <see cref="ConfigurationSource" /> for <see cref="GetMappingStrategy" />.</returns>
public static ConfigurationSource? GetMappingStrategyConfigurationSource(
this IConventionEntityType entityType)
=> entityType.FindAnnotation(RelationalAnnotationNames.MappingStrategy)
?.GetConfigurationSource();
}
Loading