diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
index c8f84558c2a..a0d76413ed8 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
@@ -77,6 +77,7 @@ private enum Id
MigrationsNotFound,
MigrationAttributeMissingWarning,
ColumnOrderIgnoredWarning,
+ PendingModelChangesWarning,
// Query events
QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500,
@@ -721,6 +722,19 @@ private static EventId MakeMigrationsId(Id id)
///
public static readonly EventId ColumnOrderIgnoredWarning = MakeMigrationsId(Id.ColumnOrderIgnoredWarning);
+ ///
+ /// Column order was ignored.
+ ///
+ ///
+ ///
+ /// This event is in the category.
+ ///
+ ///
+ /// This event uses the payload when used with a .
+ ///
+ ///
+ public static readonly EventId PendingModelChangesWarning = MakeMigrationsId(Id.PendingModelChangesWarning);
+
private static readonly string _queryPrefix = DbLoggerCategory.Query.Name + ".";
private static EventId MakeQueryId(Id id)
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
index 6cf6a0d83e7..03682b6f468 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
@@ -2309,6 +2309,40 @@ private static string MigrationAttributeMissingWarning(EventDefinitionBase defin
return d.GenerateMessage(p.MigrationType.Name);
}
+ ///
+ /// Logs for the event.
+ ///
+ /// The diagnostics logger to use.
+ /// The type being used.
+ public static void PendingModelChanges(
+ this IDiagnosticsLogger diagnostics,
+ Type contextType)
+ {
+ var definition = RelationalResources.LogPendingModelChanges(diagnostics);
+
+ if (diagnostics.ShouldLog(definition))
+ {
+ definition.Log(diagnostics, contextType);
+ }
+
+ 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)definition;
+ var p = (DbContextTypeEventData)payload;
+ return d.GenerateMessage(p.ContextType);
+ }
+
///
/// Logs for the event.
///
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
index eb2e5ca3386..680483a0d58 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
@@ -646,6 +646,15 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[EntityFrameworkInternal]
public EventDefinitionBase? LogColumnOrderIgnoredWarning;
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public EventDefinitionBase? LogPendingModelChanges;
+
///
/// 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
diff --git a/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
index c2cd5273beb..053b406f91e 100644
--- a/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalDatabaseFacadeExtensions.cs
@@ -116,6 +116,35 @@ public static async Task> GetPendingMigrationsAsync(
public static void Migrate(this DatabaseFacade databaseFacade)
=> databaseFacade.GetRelationalService().Migrate();
+ ///
+ /// Applies migrations for the context to the database. Will create the database
+ /// if it does not already exist.
+ ///
+ ///
+ /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
+ ///
+ ///
+ /// The target migration to migrate the database to, or to migrate to the latest.
+ ///
+ ///
+ ///
+ /// Note that this API is mutually exclusive with . EnsureCreated does not use migrations
+ /// to create the database and therefore the database that is created cannot be later updated using migrations.
+ ///
+ ///
+ /// See Database migrations for more information and examples.
+ ///
+ ///
+ /// The for the context.
+ [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? seed,
+ string? targetMigration = null)
+ => databaseFacade.GetRelationalService().Migrate(targetMigration, seed);
+
///
/// Asynchronously applies any pending migrations for the context to the database. Will create the database
/// if it does not already exist.
@@ -142,6 +171,40 @@ public static Task MigrateAsync(
CancellationToken cancellationToken = default)
=> databaseFacade.GetRelationalService().MigrateAsync(cancellationToken: cancellationToken);
+ ///
+ /// Asynchronously applies migrations for the context to the database. Will create the database
+ /// if it does not already exist.
+ ///
+ /// The for the context.
+ ///
+ /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
+ ///
+ ///
+ /// The target migration to migrate the database to, or to migrate to the latest.
+ ///
+ /// A to observe while waiting for the task to complete.
+ ///
+ ///
+ /// Note that this API is mutually exclusive with .
+ /// does not use migrations to create the database and therefore the database
+ /// that is created cannot be later updated using migrations.
+ ///
+ ///
+ /// See Database migrations for more information and examples.
+ ///
+ ///
+ /// A task that represents the asynchronous migration operation.
+ /// If the is canceled.
+ [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? seed,
+ string? targetMigration = null,
+ CancellationToken cancellationToken = default)
+ => databaseFacade.GetRelationalService().MigrateAsync(targetMigration, seed, cancellationToken);
+
///
/// Executes the given SQL against the database and returns the number of rows affected.
///
@@ -974,29 +1037,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();
- var migrationsAssembly = databaseFacade.GetRelationalService();
-
- var modelInitializer = databaseFacade.GetRelationalService();
-
- 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();
-
- return modelDiffer.HasDifferences(
- snapshotModel?.GetRelationalModel(),
- designTimeModel.Model.GetRelationalModel());
- }
+ => databaseFacade.GetRelationalService().HasPendingModelChanges();
private static IRelationalDatabaseFacadeDependencies GetFacadeDependencies(DatabaseFacade databaseFacade)
{
diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
index 102b26686c4..da72feae3c7 100644
--- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
+++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs
@@ -96,7 +96,8 @@ public static readonly IDictionary 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) }
};
///
diff --git a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
index 64218f235f7..b15687c3319 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
@@ -439,7 +439,8 @@ 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));
///
/// Information/metadata for a .
diff --git a/src/EFCore.Relational/Migrations/IMigrator.cs b/src/EFCore.Relational/Migrations/IMigrator.cs
index ac36e8184cc..e6b6984774b 100644
--- a/src/EFCore.Relational/Migrations/IMigrator.cs
+++ b/src/EFCore.Relational/Migrations/IMigrator.cs
@@ -32,9 +32,12 @@ public interface IMigrator
///
/// The target migration to migrate the database to, or to migrate to the latest.
///
+ ///
+ /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
+ ///
[RequiresUnreferencedCode("Migration generation currently isn't compatible with trimming")]
[RequiresDynamicCode("Migrations operations are not supported with NativeAOT")]
- void Migrate(string? targetMigration = null);
+ void Migrate(string? targetMigration = null, Action? seed = null);
///
/// Migrates the database to either a specified target migration or up to the latest
@@ -46,6 +49,9 @@ public interface IMigrator
///
/// The target migration to migrate the database to, or to migrate to the latest.
///
+ ///
+ /// The optional seed method to run after migrating the database. It will be invoked even if no migrations were applied.
+ ///
/// A to observe while waiting for the task to complete.
/// A task that represents the asynchronous operation
/// If the is canceled.
@@ -53,6 +59,7 @@ public interface IMigrator
[RequiresDynamicCode("Migrations operations are not supported with NativeAOT")]
Task MigrateAsync(
string? targetMigration = null,
+ Func? seed = null,
CancellationToken cancellationToken = default);
///
@@ -78,4 +85,16 @@ string GenerateScript(
string? fromMigration = null,
string? toMigration = null,
MigrationsSqlGenerationOptions options = MigrationsSqlGenerationOptions.Default);
+
+ ///
+ /// Returns if the model has pending changes to be applied.
+ ///
+ ///
+ /// if the database model has pending changes
+ /// and a new migration has to be added.
+ ///
+ [RequiresDynamicCode(
+ "Migrations operations are not supported with NativeAOT"
+ + " Use a migration bundle or an alternate way of executing migration operations.")]
+ bool HasPendingModelChanges();
}
diff --git a/src/EFCore.Relational/Migrations/IMigratorData.cs b/src/EFCore.Relational/Migrations/IMigratorData.cs
new file mode 100644
index 00000000000..60531a1c699
--- /dev/null
+++ b/src/EFCore.Relational/Migrations/IMigratorData.cs
@@ -0,0 +1,29 @@
+// 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.Migrations;
+
+///
+/// A class that holds the results from the last migrations application.
+///
+///
+/// See Database migrations for more information and examples.
+///
+public interface IMigratorData
+{
+ ///
+ /// The migrations that were applied to the database.
+ ///
+ public IReadOnlyList AppliedMigrations { get; }
+
+ ///
+ /// The migrations that were reverted from the database.
+ ///
+ public IReadOnlyList RevertedMigrations { get; }
+
+ ///
+ /// The target migration.
+ /// if all migrations were reverted or no target migration was specified.
+ ///
+ public Migration? TargetMigration { get; }
+}
diff --git a/src/EFCore.Relational/Migrations/IMigratorPlugin.cs b/src/EFCore.Relational/Migrations/IMigratorPlugin.cs
new file mode 100644
index 00000000000..073542af014
--- /dev/null
+++ b/src/EFCore.Relational/Migrations/IMigratorPlugin.cs
@@ -0,0 +1,47 @@
+// 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.Migrations;
+
+///
+///
+/// A service on the EF internal service provider that allows providers or extensions to execute logic
+/// after is called.
+///
+///
+/// This type is typically used by providers or extensions. It is generally not used in application code.
+///
+///
+///
+/// The service lifetime is . This means a single instance
+/// is used by many instances. The implementation must be thread-safe.
+/// This service cannot depend on services registered as .
+///
+public interface IMigratorPlugin
+{
+ ///
+ /// Called by before the seeding action.
+ ///
+ /// The that is being migrated.
+ /// The that contains the result of the migrations application.
+ ///
+ /// See Database migrations for more information and examples.
+ ///
+ void Migrated(DbContext context, IMigratorData data);
+
+ ///
+ /// Called by before the seeding action.
+ ///
+ /// The that is being migrated.
+ /// The that contains the result of the migrations application.
+ ///
+ /// See Database migrations for more information and examples.
+ ///
+ /// A to observe while waiting for the task to complete.
+ /// A task that represents the asynchronous operation
+ /// If the is canceled.
+ Task MigratedAsync(
+ DbContext context,
+ IMigratorData data,
+ CancellationToken cancellationToken = default);
+}
diff --git a/src/EFCore.Relational/Migrations/Internal/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs
index 6263bec3a9b..2f765dd4ada 100644
--- a/src/EFCore.Relational/Migrations/Internal/Migrator.cs
+++ b/src/EFCore.Relational/Migrations/Internal/Migrator.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
+
namespace Microsoft.EntityFrameworkCore.Migrations.Internal;
///
@@ -23,6 +25,9 @@ public class Migrator : IMigrator
private readonly IModelRuntimeInitializer _modelRuntimeInitializer;
private readonly IDiagnosticsLogger _logger;
private readonly IRelationalCommandDiagnosticsLogger _commandLogger;
+ private readonly IEnumerable _plugins;
+ private readonly IMigrationsModelDiffer _migrationsModelDiffer;
+ private readonly IDesignTimeModel _designTimeModel;
private readonly string _activeProvider;
///
@@ -44,7 +49,10 @@ public Migrator(
IModelRuntimeInitializer modelRuntimeInitializer,
IDiagnosticsLogger logger,
IRelationalCommandDiagnosticsLogger commandLogger,
- IDatabaseProvider databaseProvider)
+ IDatabaseProvider databaseProvider,
+ IEnumerable plugins,
+ IMigrationsModelDiffer migrationsModelDiffer,
+ IDesignTimeModel designTimeModel)
{
_migrationsAssembly = migrationsAssembly;
_historyRepository = historyRepository;
@@ -58,6 +66,9 @@ public Migrator(
_modelRuntimeInitializer = modelRuntimeInitializer;
_logger = logger;
_commandLogger = commandLogger;
+ _plugins = plugins;
+ _migrationsModelDiffer = migrationsModelDiffer;
+ _designTimeModel = designTimeModel;
_activeProvider = databaseProvider.Name;
}
@@ -75,8 +86,14 @@ public Migrator(
/// 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.
///
- public virtual void Migrate(string? targetMigration = null)
+ public virtual void Migrate(string? targetMigration, Action? seed)
{
+ if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
+ && HasPendingModelChanges())
+ {
+ _logger.PendingModelChanges(_currentContext.Context.GetType());
+ }
+
_logger.MigrateUsingConnection(this, _connection);
if (!_databaseCreator.Exists())
@@ -95,12 +112,28 @@ public virtual void Migrate(string? targetMigration = null)
_historyRepository.Create();
}
- var commandLists = GetMigrationCommandLists(_historyRepository.GetAppliedMigrations(), targetMigration);
+ PopulateMigrations(
+ _historyRepository.GetAppliedMigrations().Select(t => t.MigrationId),
+ targetMigration,
+ out var migratorData);
+ var commandLists = GetMigrationCommandLists(migratorData);
foreach (var commandList in commandLists)
{
_migrationCommandExecutor.ExecuteNonQuery(commandList(), _connection);
}
+
+ foreach (var plugin in _plugins)
+ {
+ plugin.Migrated(_currentContext.Context, migratorData);
+ }
+
+ if (seed != null)
+ {
+ using var transaction = _connection.BeginTransaction();
+ seed(_currentContext.Context, migratorData);
+ transaction.Commit();
+ }
}
finally
{
@@ -115,9 +148,16 @@ public virtual void Migrate(string? targetMigration = null)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual async Task MigrateAsync(
- string? targetMigration = null,
+ string? targetMigration,
+ Func? seed,
CancellationToken cancellationToken = default)
{
+ if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
+ && HasPendingModelChanges())
+ {
+ _logger.PendingModelChanges(_currentContext.Context.GetType());
+ }
+
_logger.MigrateUsingConnection(this, _connection);
if (!await _databaseCreator.ExistsAsync(cancellationToken).ConfigureAwait(false))
@@ -137,15 +177,30 @@ public virtual async Task MigrateAsync(
await _historyRepository.CreateAsync(cancellationToken).ConfigureAwait(false);
}
- var commandLists = GetMigrationCommandLists(
- await _historyRepository.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false),
- targetMigration);
+ PopulateMigrations(
+ (await _historyRepository.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false)).Select(t => t.MigrationId),
+ targetMigration,
+ out var migratorData);
+ var commandLists = GetMigrationCommandLists(migratorData);
foreach (var commandList in commandLists)
{
await _migrationCommandExecutor.ExecuteNonQueryAsync(commandList(), _connection, cancellationToken)
.ConfigureAwait(false);
}
+
+ foreach (var plugin in _plugins)
+ {
+ await plugin.MigratedAsync(_currentContext.Context, migratorData, cancellationToken).ConfigureAwait(false);
+ }
+
+ if (seed != null)
+ {
+ var transaction = await _connection.BeginTransactionAsync(cancellationToken).ConfigureAwait(false);
+ await using var __ = transaction.ConfigureAwait(false);
+ await seed(_currentContext.Context, migratorData, cancellationToken).ConfigureAwait(false);
+ await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
+ }
}
finally
{
@@ -153,16 +208,11 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync(commandList(), _connection,
}
}
- private IEnumerable>> GetMigrationCommandLists(
- IReadOnlyList appliedMigrationEntries,
- string? targetMigration = null)
+ private IEnumerable>> GetMigrationCommandLists(IMigratorData parameters)
{
- PopulateMigrations(
- appliedMigrationEntries.Select(t => t.MigrationId),
- targetMigration,
- out var migrationsToApply,
- out var migrationsToRevert,
- out var actualTargetMigration);
+ var migrationsToApply = parameters.AppliedMigrations;
+ var migrationsToRevert = parameters.RevertedMigrations;
+ var actualTargetMigration = parameters.TargetMigration;
for (var i = 0; i < migrationsToRevert.Count; i++)
{
@@ -206,9 +256,7 @@ private IEnumerable>> GetMigrationCommandLi
protected virtual void PopulateMigrations(
IEnumerable appliedMigrationEntries,
string? targetMigration,
- out IReadOnlyList migrationsToApply,
- out IReadOnlyList migrationsToRevert,
- out Migration? actualTargetMigration)
+ out IMigratorData parameters)
{
var appliedMigrations = new Dictionary();
var unappliedMigrations = new Dictionary();
@@ -230,6 +278,9 @@ protected virtual void PopulateMigrations(
}
}
+ IReadOnlyList migrationsToApply;
+ IReadOnlyList migrationsToRevert;
+ Migration? actualTargetMigration = null;
if (string.IsNullOrEmpty(targetMigration))
{
migrationsToApply = unappliedMigrations
@@ -237,7 +288,6 @@ protected virtual void PopulateMigrations(
.Select(p => _migrationsAssembly.CreateMigration(p.Value, _activeProvider))
.ToList();
migrationsToRevert = [];
- actualTargetMigration = null;
}
else if (targetMigration == Migration.InitialDatabase)
{
@@ -246,7 +296,6 @@ protected virtual void PopulateMigrations(
.OrderByDescending(m => m.Key)
.Select(p => _migrationsAssembly.CreateMigration(p.Value, _activeProvider))
.ToList();
- actualTargetMigration = null;
}
else
{
@@ -266,6 +315,8 @@ protected virtual void PopulateMigrations(
.Select(p => _migrationsAssembly.CreateMigration(p.Value, _activeProvider))
.SingleOrDefault();
}
+
+ parameters = new MigratorData(migrationsToApply, migrationsToRevert, actualTargetMigration);
}
///
@@ -298,12 +349,7 @@ public virtual string GenerateScript(
.Select(t => t.Key);
}
- PopulateMigrations(
- appliedMigrations,
- toMigration,
- out var migrationsToApply,
- out var migrationsToRevert,
- out var actualTargetMigration);
+ PopulateMigrations(appliedMigrations, toMigration, out var migratorData);
var builder = new IndentedStringBuilder();
@@ -318,6 +364,9 @@ public virtual string GenerateScript(
var idempotencyEnd = idempotent
? _historyRepository.GetEndIfScript()
: null;
+ var migrationsToApply = migratorData.AppliedMigrations;
+ var migrationsToRevert = migratorData.RevertedMigrations;
+ var actualTargetMigration = migratorData.TargetMigration;
for (var i = 0; i < migrationsToRevert.Count; i++)
{
var migration = migrationsToRevert[i];
@@ -445,6 +494,19 @@ protected virtual IReadOnlyList GenerateDownSql(
.ToList();
}
- private IModel FinalizeModel(IModel model)
- => _modelRuntimeInitializer.Initialize(model, designTime: true, validationLogger: null);
+ private IModel? FinalizeModel(IModel? model)
+ => model == null
+ ? null
+ : _modelRuntimeInitializer.Initialize(model);
+
+ ///
+ /// 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.
+ ///
+ public bool HasPendingModelChanges()
+ => _migrationsModelDiffer.HasDifferences(
+ FinalizeModel(_migrationsAssembly.ModelSnapshot?.Model)?.GetRelationalModel(),
+ _designTimeModel.Model.GetRelationalModel());
}
diff --git a/src/EFCore.Relational/Migrations/Internal/MigratorData.cs b/src/EFCore.Relational/Migrations/Internal/MigratorData.cs
new file mode 100644
index 00000000000..97c47555d2f
--- /dev/null
+++ b/src/EFCore.Relational/Migrations/Internal/MigratorData.cs
@@ -0,0 +1,41 @@
+// 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.Migrations.Internal;
+
+///
+/// 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.
+///
+public class MigratorData(
+ IReadOnlyList appliedMigrations,
+ IReadOnlyList revertedMigrations,
+ Migration? targetMigration)
+ : IMigratorData
+{
+ ///
+ /// 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.
+ ///
+ public IReadOnlyList AppliedMigrations { get; } = appliedMigrations;
+
+ ///
+ /// 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.
+ ///
+ public IReadOnlyList RevertedMigrations { get; } = revertedMigrations;
+
+ ///
+ /// 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.
+ ///
+ public Migration? TargetMigration { get; } = targetMigration;
+}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index b174634a215..d94c8e9877f 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -1178,7 +1178,7 @@ public static string JsonNodeMustBeHandledByProviderSpecificVisitor
=> GetString("JsonNodeMustBeHandledByProviderSpecificVisitor");
///
- /// Using parameter to access the element of a JSON collection '{entityTypeName}' is not supported when using '{asNoTrackingWithIdentityResolution}'. Use constant, or project the entire JSON entity collection instead.
+ /// Using parameter to access the element of a JSON collection '{entityTypeName}' is not supported for '{asNoTrackingWithIdentityResolution}'. Use constant, or project the entire JSON entity collection instead.
///
public static string JsonProjectingCollectionElementAccessedUsingParmeterNoTrackingWithIdentityResolution(object? entityTypeName, object? asNoTrackingWithIdentityResolution)
=> string.Format(
@@ -1188,10 +1188,10 @@ public static string JsonProjectingCollectionElementAccessedUsingParmeterNoTrack
///
/// When using '{asNoTrackingWithIdentityResolution}' entities mapped to JSON must be projected in a particular order. Project entire collection of entities '{entityTypeName}' before its individual elements.
///
- public static string JsonProjectingEntitiesIncorrectOrderNoTrackingWithIdentityResolution(object? entityTypeName, object? asNoTrackingWithIdentityResolution)
+ public static string JsonProjectingEntitiesIncorrectOrderNoTrackingWithIdentityResolution(object? asNoTrackingWithIdentityResolution, object? entityTypeName)
=> string.Format(
- GetString("JsonProjectingEntitiesIncorrectOrderNoTrackingWithIdentityResolution", nameof(entityTypeName), nameof(asNoTrackingWithIdentityResolution)),
- entityTypeName, asNoTrackingWithIdentityResolution);
+ GetString("JsonProjectingEntitiesIncorrectOrderNoTrackingWithIdentityResolution", nameof(asNoTrackingWithIdentityResolution), nameof(entityTypeName)),
+ asNoTrackingWithIdentityResolution, entityTypeName);
///
/// Projecting queryable operations on JSON collection is not supported for '{asNoTrackingWithIdentityResolution}'.
@@ -1386,7 +1386,7 @@ public static string NoActiveTransaction
=> GetString("NoActiveTransaction");
///
- /// No alias is defined on table: '{table}'
+ /// No alias is defined on table: '{table}'.
///
public static string NoAliasOnTable(object? table)
=> string.Format(
@@ -2106,7 +2106,7 @@ public static string UnsupportedOperatorForSqlExpression(object? nodeType, objec
nodeType, expressionType);
///
- /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
+ /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
///
public static string UnsupportedPropertyType(object? entity, object? property, object? clrType)
=> string.Format(
@@ -3647,6 +3647,31 @@ public static EventDefinition LogOptionalDependentWithoutIdentifyingProp
return (EventDefinition)definition;
}
+ ///
+ /// The model for context '{contextType}' has pending changes. Add a new migration before updating the database.
+ ///
+ public static EventDefinition LogPendingModelChanges(IDiagnosticsLogger logger)
+ {
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogPendingModelChanges;
+ if (definition == null)
+ {
+ definition = NonCapturingLazyInitializer.EnsureInitialized(
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogPendingModelChanges,
+ logger,
+ static logger => new EventDefinition(
+ logger.Options,
+ RelationalEventId.PendingModelChangesWarning,
+ LogLevel.Error,
+ "RelationalEventId.PendingModelChangesWarning",
+ level => LoggerMessage.Define(
+ level,
+ RelationalEventId.PendingModelChangesWarning,
+ _resourceManager.GetString("LogPendingModelChanges")!)));
+ }
+
+ return (EventDefinition)definition;
+ }
+
///
/// Possible unintended use of method 'Equals' for arguments '{left}' and '{right}' of different types in a query. This comparison will always return false.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 1e7a548235a..f974ba7aa2c 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -817,6 +817,10 @@
The entity type '{entityType}' is an optional dependent using table sharing without any required non shared property that could be used to identify whether the entity exists. If all nullable properties contain a null value in database then an object instance won't be created in the query. Add a required property to create instances with null values for other properties or mark the incoming navigation as required to always create an instance.
Warning RelationalEventId.OptionalDependentWithoutIdentifyingPropertyWarning string
+
+ The model for context '{contextType}' has pending changes. Add a new migration before updating the database.
+ Error RelationalEventId.PendingModelChangesWarning Type
+
Possible unintended use of method 'Equals' for arguments '{left}' and '{right}' of different types in a query. This comparison will always return false.
Warning RelationalEventId.QueryPossibleUnintendedUseOfEqualsWarning string string
@@ -1029,12 +1033,12 @@
Cannot create a 'SelectExpression' with a custom 'TableExpressionBase' since the result type '{entityType}' is part of a hierarchy and does not contain a discriminator property.
-
- Set operations over different entity or complex types are not supported ('{type1}' and '{type2}').
-
SelectExpression.Update() is not supported while the expression is in mutable state.
+
+ Set operations over different entity or complex types are not supported ('{type1}' and '{type2}').
+
Unable to translate set operation after client projection has been applied. Consider moving the set operation before the last 'Select' call.
diff --git a/src/EFCore.Relational/Query/RelationalAggregateMethodCallTranslatorProviderDependencies.cs b/src/EFCore.Relational/Query/RelationalAggregateMethodCallTranslatorProviderDependencies.cs
index 4474a11dd3e..6ebec3ad2c9 100644
--- a/src/EFCore.Relational/Query/RelationalAggregateMethodCallTranslatorProviderDependencies.cs
+++ b/src/EFCore.Relational/Query/RelationalAggregateMethodCallTranslatorProviderDependencies.cs
@@ -56,7 +56,7 @@ public RelationalAggregateMethodCallTranslatorProviderDependencies(
}
///
- /// The expression factory..
+ /// The expression factory.
///
public ISqlExpressionFactory SqlExpressionFactory { get; init; }
diff --git a/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs b/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs
index 6d4c628edc6..3a126739f30 100644
--- a/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs
+++ b/src/EFCore.Relational/Query/RelationalMemberTranslatorProviderDependencies.cs
@@ -54,7 +54,7 @@ public RelationalMemberTranslatorProviderDependencies(
}
///
- /// The expression factory..
+ /// The expression factory.
///
public ISqlExpressionFactory SqlExpressionFactory { get; init; }
diff --git a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
index dbff4b7a514..1a297374ef1 100644
--- a/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
+++ b/src/EFCore.SqlServer/Migrations/SqlServerMigrationsSqlGenerator.cs
@@ -1738,7 +1738,7 @@ protected virtual void Rename(
}
///
- /// Generates a transfer from one schema to another..
+ /// Generates a transfer from one schema to another.
///
/// The schema to transfer to.
/// The schema to transfer from.
diff --git a/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs b/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs
index 3dbcaed6b29..8ffa2935bce 100644
--- a/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs
+++ b/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs
@@ -1033,7 +1033,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1069,7 +1069,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1157,7 +1157,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1192,7 +1192,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1280,7 +1280,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1316,7 +1316,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1404,7 +1404,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1440,7 +1440,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1528,7 +1528,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
@@ -1564,7 +1564,7 @@ public static Task SumAsync(
/// A to observe while waiting for the task to complete.
///
/// A task that represents the asynchronous operation.
- /// The task result contains the sum of the projected values..
+ /// The task result contains the sum of the projected values.
///
///
/// or is .
diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs
index f1a76022bc8..45aa87e5bc3 100644
--- a/src/EFCore/Metadata/ITypeBase.cs
+++ b/src/EFCore/Metadata/ITypeBase.cs
@@ -195,7 +195,7 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable
new IPropertyBase? FindMember(string name);
///
- /// Gets the members with the given name on this type, base types or derived types..
+ /// Gets the members with the given name on this type, base types or derived types.
///
/// Type members.
new IEnumerable FindMembersInHierarchy(string name);
diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs
index 99ae637bde2..8faf0632fc3 100644
--- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs
+++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs
@@ -123,7 +123,10 @@ var migrationAssembly
services.GetRequiredService(),
services.GetRequiredService>(),
services.GetRequiredService(),
- services.GetRequiredService())));
+ services.GetRequiredService(),
+ services.GetServices(),
+ services.GetRequiredService(),
+ services.GetRequiredService())));
}
// ReSharper disable once UnusedTypeParameter
diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs
index a75f37bd012..9a19326d73a 100644
--- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs
@@ -4,6 +4,7 @@
// ReSharper disable InconsistentNaming
using Microsoft.EntityFrameworkCore.TestUtilities;
+using static Microsoft.EntityFrameworkCore.Migrations.MigrationsInfrastructureFixtureBase;
namespace Microsoft.EntityFrameworkCore.Migrations;
@@ -18,6 +19,7 @@ protected MigrationsInfrastructureTestBase(TFixture fixture)
{
Fixture = fixture;
Fixture.TestStore.CloseConnection();
+ Fixture.TestSqlLoggerFactory.Clear();
}
protected string Sql { get; private set; }
@@ -70,7 +72,12 @@ public virtual void Can_apply_all_migrations()
GiveMeSomeTime(db);
- db.Database.Migrate();
+ MigrationsInfrastructureFixtureBase.MigratorPlugin.ResetCounts();
+ db.Database.Migrate((c, d) =>
+ {
+ c.Add(new MigrationsInfrastructureFixtureBase.Foo { Id = 1, Bar = 10, Description = "Test" });
+ c.SaveChanges();
+ });
var history = db.GetService();
Assert.Collection(
@@ -82,6 +89,43 @@ public virtual void Can_apply_all_migrations()
x => Assert.Equal("00000000000005_Migration5", x.MigrationId),
x => Assert.Equal("00000000000006_Migration6", x.MigrationId),
x => Assert.Equal("00000000000007_Migration7", x.MigrationId));
+
+ Assert.NotNull(db.Find(1));
+
+ Assert.Equal(1, MigrationsInfrastructureFixtureBase.MigratorPlugin.MigratedCallCount);
+ Assert.Equal(0, MigrationsInfrastructureFixtureBase.MigratorPlugin.MigratedAsyncCallCount);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Can_apply_all_migrations_async()
+ {
+ using var db = Fixture.CreateContext();
+ await db.Database.EnsureDeletedAsync();
+
+ await GiveMeSomeTimeAsync(db);
+
+ MigrationsInfrastructureFixtureBase.MigratorPlugin.ResetCounts();
+ await db.Database.MigrateAsync(async (c, d, ct) =>
+ {
+ c.Add(new MigrationsInfrastructureFixtureBase.Foo { Id = 1, Bar = 10, Description = "Test" });
+ await c.SaveChangesAsync(ct);
+ });
+
+ var history = db.GetService();
+ Assert.Collection(
+ await history.GetAppliedMigrationsAsync(),
+ x => Assert.Equal("00000000000001_Migration1", x.MigrationId),
+ x => Assert.Equal("00000000000002_Migration2", x.MigrationId),
+ x => Assert.Equal("00000000000003_Migration3", x.MigrationId),
+ x => Assert.Equal("00000000000004_Migration4", x.MigrationId),
+ x => Assert.Equal("00000000000005_Migration5", x.MigrationId),
+ x => Assert.Equal("00000000000006_Migration6", x.MigrationId),
+ x => Assert.Equal("00000000000007_Migration7", x.MigrationId));
+
+ Assert.NotNull(await db.FindAsync(1));
+
+ Assert.Equal(0, MigrationsInfrastructureFixtureBase.MigratorPlugin.MigratedCallCount);
+ Assert.Equal(1, MigrationsInfrastructureFixtureBase.MigratorPlugin.MigratedAsyncCallCount);
}
[ConditionalFact]
@@ -92,8 +136,7 @@ public virtual void Can_apply_range_of_migrations()
GiveMeSomeTime(db);
- var migrator = db.GetService();
- migrator.Migrate("Migration6");
+ db.Database.Migrate(null, "Migration6");
var history = db.GetService();
Assert.Collection(
@@ -121,6 +164,9 @@ public virtual void Can_apply_one_migration()
Assert.Collection(
history.GetAppliedMigrations(),
x => Assert.Equal("00000000000001_Migration1", x.MigrationId));
+
+ Assert.Equal(LogLevel.Error,
+ Fixture.TestSqlLoggerFactory.Log.Single(l => l.Id == RelationalEventId.PendingModelChangesWarning).Level);
}
[ConditionalFact]
@@ -160,28 +206,6 @@ public virtual void Can_revert_one_migrations()
x => Assert.Equal("00000000000004_Migration4", x.MigrationId));
}
- [ConditionalFact]
- public virtual async Task Can_apply_all_migrations_async()
- {
- using var db = Fixture.CreateContext();
- await db.Database.EnsureDeletedAsync();
-
- await GiveMeSomeTimeAsync(db);
-
- await db.Database.MigrateAsync();
-
- var history = db.GetService();
- Assert.Collection(
- await history.GetAppliedMigrationsAsync(),
- x => Assert.Equal("00000000000001_Migration1", x.MigrationId),
- x => Assert.Equal("00000000000002_Migration2", x.MigrationId),
- x => Assert.Equal("00000000000003_Migration3", x.MigrationId),
- x => Assert.Equal("00000000000004_Migration4", x.MigrationId),
- x => Assert.Equal("00000000000005_Migration5", x.MigrationId),
- x => Assert.Equal("00000000000006_Migration6", x.MigrationId),
- x => Assert.Equal("00000000000007_Migration7", x.MigrationId));
- }
-
[ConditionalFact]
public virtual void Can_apply_one_migration_in_parallel()
{
@@ -444,12 +468,16 @@ public abstract class MigrationsInfrastructureFixtureBase
protected override IServiceCollection AddServices(IServiceCollection serviceCollection)
{
TestStore.UseConnectionString = true;
- return base.AddServices(serviceCollection);
+ return base.AddServices(serviceCollection)
+ .AddSingleton();
}
protected override string StoreName
=> "MigrationsTest";
+ public TestSqlLoggerFactory TestSqlLoggerFactory
+ => (TestSqlLoggerFactory)ListLoggerFactory;
+
public EmptyMigrationsContext CreateEmptyContext()
=> new(
TestStore.AddProviderOptions(
@@ -460,9 +488,6 @@ public EmptyMigrationsContext CreateEmptyContext()
.BuildServiceProvider(validateScopes: true))
.Options);
- public new virtual MigrationsContext CreateContext()
- => base.CreateContext();
-
public class EmptyMigrationsContext(DbContextOptions options) : DbContext(options);
public class MigrationsContext(DbContextOptions options) : PoolableDbContext(options)
@@ -470,12 +495,45 @@ public class MigrationsContext(DbContextOptions options) : PoolableDbContext(opt
public DbSet Foos { get; set; }
}
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ modelBuilder.Entity(b => b.ToTable("Table1"));
+ }
+
+ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
+ => base.AddOptions(builder).ConfigureWarnings(e => e.Log(RelationalEventId.PendingModelChangesWarning));
+
+ protected override bool ShouldLogCategory(string logCategory)
+ => logCategory == DbLoggerCategory.Migrations.Name;
+
public class Foo
{
public int Id { get; set; }
+ public int Bar { get; set; }
public string Description { get; set; }
}
+ public class MigratorPlugin : IMigratorPlugin
+ {
+ public static int MigratedCallCount { get; private set; }
+ public static int MigratedAsyncCallCount { get; private set; }
+
+ public static void ResetCounts()
+ {
+ MigratedCallCount = 0;
+ MigratedAsyncCallCount = 0;
+ }
+
+ public void Migrated(DbContext context, IMigratorData data)
+ => MigratedCallCount++;
+
+ public Task MigratedAsync(DbContext context, IMigratorData data, CancellationToken cancellationToken)
+ {
+ MigratedAsyncCallCount++;
+ return Task.CompletedTask;
+ }
+ }
+
[DbContext(typeof(MigrationsContext))]
[Migration("00000000000001_Migration1")]
private class Migration1 : Migration
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs
index 2c5943573bb..2bca0105019 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalEventIdTest.cs
@@ -146,6 +146,9 @@ public string GenerateScript(
string toMigration = null,
MigrationsSqlGenerationOptions options = MigrationsSqlGenerationOptions.Default)
=> throw new NotImplementedException();
+ public void Migrate(string targetMigration = null, Action seed = null) => throw new NotImplementedException();
+ public Task MigrateAsync(string targetMigration = null, Func seed = null, CancellationToken cancellationToken = default) => throw new NotImplementedException();
+ public bool HasPendingModelChanges() => throw new NotImplementedException();
}
private class FakeMigrationsAssembly : IMigrationsAssembly
diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs
index a6561f78f0c..acbdcf13877 100644
--- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs
+++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs
@@ -2192,7 +2192,7 @@ public virtual void InversePropertyAttribute_pointing_to_same_nav_on_base_causes
+ $" {nameof(MultipleAnswersInverse)}.{nameof(MultipleAnswersInverse.Answers)}",
nameof(PartialAnswerInverse.Answer)),
"CoreEventId.MultipleInversePropertiesSameTargetWarning"),
- Assert.Throws(() => modelBuilder.FinalizeModel()).Message);
+ Assert.Throws(modelBuilder.FinalizeModel).Message);
}
[ConditionalFact]
diff --git a/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs b/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs
index 79dc7e6a916..e1ed535fa12 100644
--- a/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs
+++ b/test/EFCore.Specification.Tests/TestUtilities/ListLoggerFactory.cs
@@ -48,12 +48,7 @@ public virtual ILogger CreateLogger(string name)
}
private void CheckDisposed()
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(nameof(ListLoggerFactory));
- }
- }
+ => ObjectDisposedException.ThrowIf(_disposed, typeof(ListLoggerFactory));
public void AddProvider(ILoggerProvider provider)
=> CheckDisposed();
diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs
index c3f6e105bdd..03a4e7c57b3 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsInfrastructureSqlServerTest.cs
@@ -3,6 +3,7 @@
using Identity30.Data;
using Microsoft.Data.SqlClient;
+using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
using Microsoft.EntityFrameworkCore.TestModels.AspNetIdentity;
@@ -16,10 +17,10 @@ public class MigrationsInfrastructureSqlServerTest(MigrationsInfrastructureSqlSe
: MigrationsInfrastructureTestBase(fixture)
{
public override void Can_apply_all_migrations() // Issue #32826
- => Assert.Throws(() => base.Can_apply_all_migrations());
+ => Assert.Throws(base.Can_apply_all_migrations);
public override Task Can_apply_all_migrations_async() // Issue #32826
- => Assert.ThrowsAsync(() => base.Can_apply_all_migrations_async());
+ => Assert.ThrowsAsync(base.Can_apply_all_migrations_async);
public override void Can_generate_migration_from_initial_database_to_initial()
{
@@ -989,11 +990,44 @@ public override void Can_get_active_provider()
}
[ConditionalFact]
- public async Task Empty_Migration_Creates_Database()
+ public void Throws_for_pending_model_changes()
+ {
+ using var context = new BloggingContext(
+ Fixture.TestStore.AddProviderOptions(
+ new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options);
+
+ Assert.Equal(
+ CoreStrings.WarningAsErrorTemplate(
+ RelationalEventId.PendingModelChangesWarning.ToString(),
+ RelationalResources.LogPendingModelChanges(new TestLogger())
+ .GenerateMessage(typeof(BloggingContext)),
+ "RelationalEventId.PendingModelChangesWarning"),
+ (Assert.Throws(context.Database.Migrate)).Message);
+ }
+
+ [ConditionalFact]
+ public async Task Throws_for_pending_model_changes_async()
{
using var context = new BloggingContext(
Fixture.TestStore.AddProviderOptions(
new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options);
+
+ Assert.Equal(
+ CoreStrings.WarningAsErrorTemplate(
+ RelationalEventId.PendingModelChangesWarning.ToString(),
+ RelationalResources.LogPendingModelChanges(new TestLogger())
+ .GenerateMessage(typeof(BloggingContext)),
+ "RelationalEventId.PendingModelChangesWarning"),
+ (await Assert.ThrowsAsync(() => context.Database.MigrateAsync())).Message);
+ }
+
+ [ConditionalFact]
+ public async Task Empty_Migration_Creates_Database()
+ {
+ using var context = new BloggingContext(
+ Fixture.TestStore.AddProviderOptions(
+ new DbContextOptionsBuilder().EnableServiceProviderCaching(false))
+ .ConfigureWarnings(e => e.Log(RelationalEventId.PendingModelChangesWarning)).Options);
var creator = (SqlServerDatabaseCreator)context.GetService();
creator.RetryTimeout = TimeSpan.FromMinutes(10);
@@ -1004,7 +1038,6 @@ public async Task Empty_Migration_Creates_Database()
private class BloggingContext(DbContextOptions options) : DbContext(options)
{
-
// ReSharper disable once UnusedMember.Local
public DbSet Blogs { get; set; }