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

Execute migrations using the ExecutionStrategy #34206

Merged
merged 2 commits into from
Jul 26, 2024
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
50 changes: 50 additions & 0 deletions src/EFCore.Relational/Diagnostics/MigrationCommandEventData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Diagnostics;

/// <summary>
/// The <see cref="DiagnosticSource" /> event payload for
/// <see cref="RelationalEventId" /> events of a specific migration.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-diagnostics">Logging, events, and diagnostics</see> for more information and examples.
/// </remarks>
public class MigrationCommandEventData : MigratorEventData
{
/// <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="migrator">
/// The <see cref="IMigrator" /> in use.
/// </param>
/// <param name="migration">
/// The <see cref="Migration" /> being processed.
/// </param>
/// <param name="command">
/// The <see cref="MigrationCommand" /> being processed.
/// </param>
public MigrationCommandEventData(
EventDefinitionBase eventDefinition,
Func<EventDefinitionBase, EventData, string> messageGenerator,
IMigrator migrator,
Migration migration,
MigrationCommand command)
: base(eventDefinition, messageGenerator, migrator)
{
Migration = migration;
MigrationCommand = command;
}

/// <summary>
/// The <see cref="Migration" /> being processed.
/// </summary>
public virtual Migration Migration { get; }

/// <summary>
/// The <see cref="MigrationCommand" /> being processed.
/// </summary>
public virtual MigrationCommand MigrationCommand { 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 @@ -77,6 +77,8 @@ private enum Id
MigrationsNotFound,
MigrationAttributeMissingWarning,
ColumnOrderIgnoredWarning,
PendingModelChangesWarning,
NonTransactionalMigrationOperationWarning,

// Query events
QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500,
Expand Down Expand Up @@ -721,6 +723,32 @@ private static EventId MakeMigrationsId(Id id)
/// </remarks>
public static readonly EventId ColumnOrderIgnoredWarning = MakeMigrationsId(Id.ColumnOrderIgnoredWarning);

/// <summary>
/// The model contains changes compared to the last migration.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Migrations" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="DbContextTypeEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId PendingModelChangesWarning = MakeMigrationsId(Id.PendingModelChangesWarning);

/// <summary>
/// A migration contains a non-transactional operation.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Migrations" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="MigrationCommandEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId NonTransactionalMigrationOperationWarning = MakeMigrationsId(Id.NonTransactionalMigrationOperationWarning);

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

private static EventId MakeQueryId(Id id)
Expand Down
84 changes: 84 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2309,6 +2309,90 @@ private static string MigrationAttributeMissingWarning(EventDefinitionBase defin
return d.GenerateMessage(p.MigrationType.Name);
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.PendingModelChangesWarning" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
/// <param name="contextType">The <see cref="DbContext" /> type being used.</param>
public static void PendingModelChangesWarning(
this IDiagnosticsLogger<DbLoggerCategory.Migrations> diagnostics,
Type contextType)
{
var definition = RelationalResources.LogPendingModelChanges(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, contextType.ShortDisplayName());
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new DbContextTypeEventData(
definition,
PendingModelChanges,
contextType);

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

private static string PendingModelChanges(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string>)definition;
var p = (DbContextTypeEventData)payload;
return d.GenerateMessage(p.ContextType.ShortDisplayName());
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.NonTransactionalMigrationOperationWarning" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
/// <param name="migrator">The <see cref="IMigrator" /> in use.</param>
/// <param name="migration">The <see cref="Migration" /> being processed.</param>
/// <param name="command">The <see cref="MigrationCommand" /> being processed.</param>
public static void NonTransactionalMigrationOperationWarning(
this IDiagnosticsLogger<DbLoggerCategory.Migrations> diagnostics,
IMigrator migrator,
Migration migration,
MigrationCommand command)
{
var definition = RelationalResources.LogNonTransactionalMigrationOperationWarning(diagnostics);

if (diagnostics.ShouldLog(definition))
{
var commandText = command.CommandText;
if (commandText.Length > 100)
{
commandText = commandText.Substring(0, 100) + "...";
}
definition.Log(diagnostics, commandText, migration.GetType().ShortDisplayName());
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new MigrationCommandEventData(
definition,
NonTransactionalMigrationOperationWarning,
migrator,
migration,
command);

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

private static string NonTransactionalMigrationOperationWarning(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string, string>)definition;
var p = (MigrationCommandEventData)payload;
var commandText = p.MigrationCommand.CommandText;
if (commandText.Length > 100)
{
commandText = commandText.Substring(0, 100) + "...";
}
return d.GenerateMessage(commandText, p.Migration.GetType().ShortDisplayName());
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning" /> event.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,24 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[EntityFrameworkInternal]
public EventDefinitionBase? LogColumnOrderIgnoredWarning;

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

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

/// <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 @@ -116,6 +116,40 @@ public static async Task<IEnumerable<string>> GetPendingMigrationsAsync(
public static void Migrate(this DatabaseFacade databaseFacade)
=> databaseFacade.GetRelationalService<IMigrator>().Migrate();

/// <summary>
/// Applies migrations for the context to the database. Will create the database
/// if it does not already exist.
/// </summary>
/// <param name="targetMigration">
/// The target migration to migrate the database to, or <see langword="null" /> to migrate to the latest.
/// </param>
/// <param name="seed">
/// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
/// </param>
/// <param name="lockTimeout">
/// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
/// lock is released when the migration operation completes.
/// </param>
/// <remarks>
/// <para>
/// Note that this API is mutually exclusive with <see cref="DatabaseFacade.EnsureCreated" />. EnsureCreated does not use migrations
/// to create the database and therefore the database that is created cannot be later updated using migrations.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information and examples.
/// </para>
/// </remarks>
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
[RequiresDynamicCode(
"Migrations operations are not supported with NativeAOT"
+ " Use a migration bundle or an alternate way of executing migration operations.")]
public static void Migrate(
this DatabaseFacade databaseFacade,
Action<DbContext, IMigratorData>? seed,
string? targetMigration = null,
TimeSpan? lockTimeout = null)
=> databaseFacade.GetRelationalService<IMigrator>().Migrate(targetMigration, seed, lockTimeout);

/// <summary>
/// Asynchronously applies any pending migrations for the context to the database. Will create the database
/// if it does not already exist.
Expand All @@ -142,6 +176,45 @@ public static Task MigrateAsync(
CancellationToken cancellationToken = default)
=> databaseFacade.GetRelationalService<IMigrator>().MigrateAsync(cancellationToken: cancellationToken);

/// <summary>
/// Asynchronously applies migrations for the context to the database. Will create the database
/// if it does not already exist.
/// </summary>
/// <param name="databaseFacade">The <see cref="DatabaseFacade" /> for the context.</param>
/// <param name="targetMigration">
/// The target migration to migrate the database to, or <see langword="null" /> to migrate to the latest.
/// </param>
/// <param name="seed">
/// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
/// </param>
/// <param name="lockTimeout">
/// The maximum amount of time that the migration lock should be held. Unless a catastrophic failure occurs, the
/// lock is released when the migration operation completes.
/// </param>
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
/// <remarks>
/// <para>
/// Note that this API is mutually exclusive with <see cref="DatabaseFacade.EnsureCreated" />.
/// <see cref="DatabaseFacade.EnsureCreated" /> does not use migrations to create the database and therefore the database
/// that is created cannot be later updated using migrations.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information and examples.
/// </para>
/// </remarks>
/// <returns>A task that represents the asynchronous migration operation.</returns>
/// <exception cref="OperationCanceledException">If the <see cref="CancellationToken" /> is canceled.</exception>
[RequiresDynamicCode(
"Migrations operations are not supported with NativeAOT"
+ " Use a migration bundle or an alternate way of executing migration operations.")]
public static Task MigrateAsync(
this DatabaseFacade databaseFacade,
Func<DbContext, IMigratorData, CancellationToken, Task>? seed,
string? targetMigration = null,
TimeSpan? lockTimeout = null,
CancellationToken cancellationToken = default)
=> databaseFacade.GetRelationalService<IMigrator>().MigrateAsync(targetMigration, seed, lockTimeout, cancellationToken);

/// <summary>
/// Executes the given SQL against the database and returns the number of rows affected.
/// </summary>
Expand Down Expand Up @@ -974,29 +1047,7 @@ public static bool IsRelational(this DatabaseFacade databaseFacade)
"Migrations operations are not supported with NativeAOT"
+ " Use a migration bundle or an alternate way of executing migration operations.")]
public static bool HasPendingModelChanges(this DatabaseFacade databaseFacade)
{
var modelDiffer = databaseFacade.GetRelationalService<IMigrationsModelDiffer>();
var migrationsAssembly = databaseFacade.GetRelationalService<IMigrationsAssembly>();

var modelInitializer = databaseFacade.GetRelationalService<IModelRuntimeInitializer>();

var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;
if (snapshotModel is IMutableModel mutableModel)
{
snapshotModel = mutableModel.FinalizeModel();
}

if (snapshotModel is not null)
{
snapshotModel = modelInitializer.Initialize(snapshotModel);
}

var designTimeModel = databaseFacade.GetRelationalService<IDesignTimeModel>();

return modelDiffer.HasDifferences(
snapshotModel?.GetRelationalModel(),
designTimeModel.Model.GetRelationalModel());
}
=> databaseFacade.GetRelationalService<IMigrator>().HasPendingModelChanges();

private static IRelationalDatabaseFacadeDependencies GetFacadeDependencies(DatabaseFacade databaseFacade)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
{ typeof(ISqlGenerationHelper), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IRelationalAnnotationProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMigrationsAnnotationProvider), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMigrationCommandExecutor), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IMigrationCommandExecutor), new ServiceCharacteristics(ServiceLifetime.Scoped) },
{ typeof(IRelationalTypeMappingSource), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IUpdateSqlGenerator), new ServiceCharacteristics(ServiceLifetime.Singleton) },
{ typeof(IRelationalTransactionFactory), new ServiceCharacteristics(ServiceLifetime.Singleton) },
Expand Down Expand Up @@ -96,7 +96,8 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
typeof(IAggregateMethodCallTranslatorPlugin),
new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true)
},
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IMigratorPlugin), new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true) }
};

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,9 @@ public static CoreOptionsExtension WithDefaultWarningConfiguration(CoreOptionsEx
.TryWithExplicit(RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable, WarningBehavior.Throw)
.TryWithExplicit(RelationalEventId.IndexPropertiesMappedToNonOverlappingTables, WarningBehavior.Throw)
.TryWithExplicit(RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables, WarningBehavior.Throw)
.TryWithExplicit(RelationalEventId.StoredProcedureConcurrencyTokenNotMapped, WarningBehavior.Throw));
.TryWithExplicit(RelationalEventId.StoredProcedureConcurrencyTokenNotMapped, WarningBehavior.Throw)
.TryWithExplicit(RelationalEventId.PendingModelChangesWarning, WarningBehavior.Throw)
.TryWithExplicit(RelationalEventId.NonTransactionalMigrationOperationWarning, WarningBehavior.Throw));

/// <summary>
/// Information/metadata for a <see cref="RelationalOptionsExtension" />.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ namespace Microsoft.EntityFrameworkCore.Migrations;
/// </summary>
/// <remarks>
/// <para>
/// The service lifetime is <see cref="ServiceLifetime.Singleton" />. This means a single instance
/// is used by many <see cref="DbContext" /> instances. The implementation must be thread-safe.
/// This service cannot depend on services registered as <see cref="ServiceLifetime.Scoped" />.
/// The service lifetime is <see cref="ServiceLifetime.Scoped" />. This means that each
/// <see cref="DbContext" /> instance will use its own instance of this service.
/// The implementation may depend on other services registered with any lifetime.
/// The implementation does not need to be thread-safe.
/// </para>
/// <para>
/// See <see href="https://aka.ms/efcore-docs-migrations">Database migrations</see> for more information and examples.
Expand Down
Loading
Loading