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 10, 2021
1 parent 647a6e1 commit 5053a75
Show file tree
Hide file tree
Showing 35 changed files with 754 additions and 70 deletions.
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.

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
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
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
Expand Up @@ -55,6 +55,10 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator
= 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 void RemoveAnnotationsHandledByConventions(IIndex index, IDiction
annotations,
RelationalAnnotationNames.ColumnName, _propertyHasColumnNameMethodInfo, methodCallCodeFragments);

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

if (TryGetAndRemove(annotations, RelationalAnnotationNames.DefaultValueSql, out string? defaultValueSql))
{
methodCallCodeFragments.Add(
Expand Down
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
@@ -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; }
}
}
@@ -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
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
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
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;
}
}

0 comments on commit 5053a75

Please sign in to comment.