Skip to content

Commit

Permalink
Migrations: Honor [Column(Order)] in CreateTable operation
Browse files Browse the repository at this point in the history
Resolves dotnet#10059
  • Loading branch information
bricelam committed Sep 11, 2021
1 parent 8098a16 commit f2eb6f5
Show file tree
Hide file tree
Showing 35 changed files with 755 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ public static class ScaffoldingAnnotationNames
/// </summary>
public const string Prefix = "Scaffolding:";

/// <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 const string ColumnOrdinal = Prefix + "ColumnOrdinal";

/// <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

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ private void GenerateProperty(IProperty property)
.FilterIgnoredAnnotations(property.GetAnnotations())
.ToDictionary(a => a.Name, a => a);
_annotationCodeGenerator.RemoveAnnotationsHandledByConventions(property, annotations);
annotations.Remove(ScaffoldingAnnotationNames.ColumnOrdinal);
annotations.Remove(RelationalAnnotationNames.ColumnOrder);

if (_useDataAnnotations)
{
Expand Down Expand Up @@ -879,7 +879,7 @@ private void GenerateManyToMany(ISkipNavigation skipNavigation)
.FilterIgnoredAnnotations(property.GetAnnotations())
.ToDictionary(a => a.Name, a => a);
_annotationCodeGenerator.RemoveAnnotationsHandledByConventions(property, propertyAnnotations);
propertyAnnotations.Remove(ScaffoldingAnnotationNames.ColumnOrdinal);
propertyAnnotations.Remove(RelationalAnnotationNames.ColumnOrder);

if ((!_useNullableReferenceTypes || property.ClrType.IsValueType)
&& !property.IsNullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ protected virtual void GenerateProperties(IEntityType entityType)
{
Check.NotNull(entityType, nameof(entityType));

foreach (var property in entityType.GetProperties().OrderBy(p => p.GetColumnOrdinal()))
foreach (var property in entityType.GetProperties().OrderBy(p => p.GetColumnOrder() ?? -1))
{
GenerateComment(property.GetComment());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ protected virtual EntityTypeBuilder VisitColumns(EntityTypeBuilder builder, ICol
property.IsConcurrencyToken();
}

property.Metadata.SetColumnOrdinal(column.Table.Columns.IndexOf(column));
property.Metadata.SetColumnOrder(column.Table.Columns.IndexOf(column));

property.Metadata.AddAnnotations(
column.GetAnnotations().Where(
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore.Relational/Design/AnnotationCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ private static readonly MethodInfo _propertyHasColumnNameMethodInfo
= typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod(
nameof(RelationalPropertyBuilderExtensions.HasColumnName), typeof(PropertyBuilder), typeof(string));

private static readonly MethodInfo _propertyHasColumnOrderMethodInfo
= typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod(
nameof(RelationalPropertyBuilderExtensions.HasColumnOrder), typeof(PropertyBuilder), typeof(int?));

private static readonly MethodInfo _propertyHasDefaultValueSqlMethodInfo1
= typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod(
nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql), typeof(PropertyBuilder));
Expand Down Expand Up @@ -221,6 +225,10 @@ public virtual IReadOnlyList<MethodCallCodeFragment> GenerateFluentApiCalls(
annotations,
RelationalAnnotationNames.ColumnName, _propertyHasColumnNameMethodInfo, methodCallCodeFragments);

GenerateSimpleFluentApiCall(
annotations,
RelationalAnnotationNames.ColumnOrder, _propertyHasColumnOrderMethodInfo, methodCallCodeFragments);

if (TryGetAndRemove(annotations, RelationalAnnotationNames.DefaultValueSql, out string? defaultValueSql))
{
methodCallCodeFragments.Add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen
}
else
{
annotations.Remove(RelationalAnnotationNames.ColumnOrder);
annotations.Remove(RelationalAnnotationNames.Comment);
annotations.Remove(RelationalAnnotationNames.Collation);

Expand Down
46 changes: 46 additions & 0 deletions src/EFCore.Relational/Diagnostics/ColumnsEventData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore.Diagnostics
{
/// <summary>
/// A <see cref="DiagnosticSource" /> event payload class for events that have columns.
/// </summary>
public class ColumnsEventData : EventData
{
/// <summary>
/// Constructs the event payload.
/// </summary>
/// <param name="eventDefinition"> The event definition. </param>
/// <param name="messageGenerator"> A delegate that generates a log message for this event. </param>
/// <param name="storeObject"> The table. </param>
/// <param name="columns"> The columns. </param>
public ColumnsEventData(
EventDefinitionBase eventDefinition,
Func<EventDefinitionBase, EventData, string> messageGenerator,
StoreObjectIdentifier storeObject,
IReadOnlyList<string> columns)
: base(eventDefinition, messageGenerator)
{
StoreObject = storeObject;
Columns = columns;
}

/// <summary>
/// Gets the table.
/// </summary>
/// <value> The table. </value>
public virtual StoreObjectIdentifier StoreObject { get; }

/// <summary>
/// Gets the columns.
/// </summary>
/// <value> The columns. </value>
public virtual IReadOnlyList<string> Columns { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using Microsoft.EntityFrameworkCore.Migrations.Operations;

namespace Microsoft.EntityFrameworkCore.Diagnostics
{
/// <summary>
/// The <see cref="DiagnosticSource"/> event payload for events that reference a Migrations column operation.
/// </summary>
public class MigrationColumnOperationEventData : EventData
{
/// <summary>
/// Initializes a new instance of the <see cref="MigrationColumnOperationEventData"/> class.
/// </summary>
/// <param name="eventDefinition"> The event definition. </param>
/// <param name="messageGenerator"> A delegate that generates a log message for this event. </param>
/// <param name="columnOperation"> The column operation. </param>
public MigrationColumnOperationEventData(
EventDefinitionBase eventDefinition,
Func<EventDefinitionBase, EventData, string> messageGenerator,
ColumnOperation columnOperation)
: base(eventDefinition, messageGenerator)
=> ColumnOperation = columnOperation;

/// <summary>
/// Gets the column operation.
/// </summary>
/// <value> The column operation. </value>
public virtual ColumnOperation ColumnOperation { get; }
}
}
28 changes: 28 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ private enum Id
MigrationsNotApplied,
MigrationsNotFound,
MigrationAttributeMissingWarning,
ColumnOrderIgnoredWarning,

// Query events
QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500,
Expand All @@ -88,6 +89,7 @@ private enum Id
IndexPropertiesMappedToNonOverlappingTables,
ForeignKeyPropertiesMappedToUnrelatedTables,
OptionalDependentWithoutIdentifyingPropertyWarning,
DuplicateColumnOrders,

// Update events
BatchReadyForExecution = CoreEventId.RelationalBaseId + 700,
Expand Down Expand Up @@ -598,6 +600,19 @@ private static EventId MakeMigrationsId(Id id)
/// </summary>
public static readonly EventId MigrationAttributeMissingWarning = MakeMigrationsId(Id.MigrationAttributeMissingWarning);

/// <summary>
/// <para>
/// Column order was ignored.
/// </para>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Migrations" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="MigrationColumnOperationEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </summary>
public static readonly EventId ColumnOrderIgnoredWarning = MakeMigrationsId(Id.ColumnOrderIgnoredWarning);

private static readonly string _queryPrefix = DbLoggerCategory.Query.Name + ".";

private static EventId MakeQueryId(Id id)
Expand Down Expand Up @@ -739,6 +754,19 @@ private static EventId MakeValidationId(Id id)
public static readonly EventId OptionalDependentWithoutIdentifyingPropertyWarning
= MakeValidationId(Id.OptionalDependentWithoutIdentifyingPropertyWarning);

/// <summary>
/// <para>
/// The configured column orders for a table contains duplicates.
/// </para>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Model.Validation" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="ColumnsEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </summary>
public static readonly EventId DuplicateColumnOrders = MakeValidationId(Id.DuplicateColumnOrders);

private static readonly string _updatePrefix = DbLoggerCategory.Update.Name + ".";

private static EventId MakeUpdateId(Id id)
Expand Down
76 changes: 74 additions & 2 deletions src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
Expand All @@ -18,9 +17,9 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.Update;
using Microsoft.EntityFrameworkCore.Utilities;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -3033,5 +3032,78 @@ private static string OptionalDependentWithAllNullPropertiesWarningSensitive(Eve
var p = (UpdateEntryEventData)payload;
return d.GenerateMessage(p.EntityEntry.EntityType.DisplayName(), p.EntityEntry.BuildCurrentValuesString(p.EntityEntry.EntityType.FindPrimaryKey()!.Properties));
}

/// <summary>
/// Logs the <see cref="RelationalEventId.DuplicateColumnOrders" /> event.
/// </summary>
/// <param name="diagnostics"> The diagnostics logger to use. </param>
/// <param name="storeObject"> The table. </param>
/// <param name="columns"> The columns. </param>
public static void DuplicateColumnOrders(
this IDiagnosticsLogger<DbLoggerCategory.Model.Validation> diagnostics,
StoreObjectIdentifier storeObject,
IReadOnlyList<string> columns)
{
var definition = RelationalResources.LogDuplicateColumnOrders(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, storeObject.DisplayName(), string.Join(", ", columns.Select(c => "'" + c + "'")));
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new ColumnsEventData(
definition,
DuplicateColumnOrders,
storeObject,
columns);

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

private static string DuplicateColumnOrders(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string, string>)definition;
var p = (ColumnsEventData)payload;

return d.GenerateMessage(p.StoreObject.DisplayName(), string.Join(", ", p.Columns.Select(c => "'" + c + "'")));
}

/// <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 static void ColumnOrderIgnoredWarning(
this IDiagnosticsLogger<DbLoggerCategory.Migrations> diagnostics,
ColumnOperation operation)
{
var definition = RelationalResources.LogColumnOrderIgnoredWarning(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, (operation.Table, operation.Schema).FormatTable(), operation.Name);
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new MigrationColumnOperationEventData(
definition,
ColumnOrderIgnoredWarning,
operation);

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

private static string ColumnOrderIgnoredWarning(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string, string>)definition;
var p = (MigrationColumnOperationEventData)payload;
return d.GenerateMessage((p.ColumnOperation.Table, p.ColumnOperation.Schema).FormatTable(), p.ColumnOperation.Name);
}
}
}
18 changes: 18 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -530,5 +530,23 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
/// </summary>
[EntityFrameworkInternal]
public EventDefinitionBase? LogOptionalDependentWithAllNullPropertiesSensitive;

/// <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? LogDuplicateColumnOrders;

/// <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? LogColumnOrderIgnoredWarning;
}
}
Loading

0 comments on commit f2eb6f5

Please sign in to comment.