Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ public virtual MigrationFiles RemoveMigration(
model = Dependencies.SnapshotModelProcessor.Process(migration.TargetModel);

if (!Dependencies.MigrationsModelDiffer.HasDifferences(
model.GetRelationalModel(), Dependencies.SnapshotModelProcessor.Process(modelSnapshot.Model).GetRelationalModel()))
model.GetRelationalModel(), Dependencies.SnapshotModelProcessor.Process(modelSnapshot.Model, resetVersion: true).GetRelationalModel()))
{
var applied = false;

Expand Down
34 changes: 34 additions & 0 deletions src/EFCore.Relational/Diagnostics/MigrationVersionEventData.cs
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.

namespace Microsoft.EntityFrameworkCore.Diagnostics;

/// <summary>
/// The <see cref="DiagnosticSource" /> event payload for
/// <see cref="RelationalEventId.OldMigrationVersionWarning" />.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-diagnostics">Logging, events, and diagnostics</see> for more information and examples.
/// </remarks>
public class MigrationVersionEventData : DbContextTypeEventData
{
/// <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="contextType">The <see cref="DbContext" /> type.</param>
/// <param name="migrationVersion">The EF Core version that was used to create the model snapshot.</param>
public MigrationVersionEventData(
EventDefinitionBase eventDefinition,
Func<EventDefinitionBase, EventData, string> messageGenerator,
Type contextType,
string? migrationVersion)
: base(eventDefinition, messageGenerator, contextType)
=> MigrationVersion = migrationVersion;

/// <summary>
/// The EF Core version that was used to create the model snapshot, or <see langword="null" /> if it is not known.
/// </summary>
public virtual string? MigrationVersion { get; }
}
14 changes: 14 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ private enum Id
AcquiringMigrationLock,
MigrationsUserTransactionWarning,
ModelSnapshotNotFound,
OldMigrationVersionWarning,

// Query events
QueryClientEvaluationWarning = CoreEventId.RelationalBaseId + 500,
Expand Down Expand Up @@ -806,6 +807,19 @@ private static EventId MakeMigrationsId(Id id)
/// </remarks>
public static readonly EventId ModelSnapshotNotFound = MakeMigrationsId(Id.ModelSnapshotNotFound);

/// <summary>
/// The last migration was created with an older version of EF Core.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Migrations" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="MigrationVersionEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId OldMigrationVersionWarning = MakeMigrationsId(Id.OldMigrationVersionWarning);

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

private static EventId MakeQueryId(Id id)
Expand Down
37 changes: 37 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2414,6 +2414,43 @@ private static string ModelSnapshotNotFound(EventDefinitionBase definition, Even
return d.GenerateMessage(p.MigrationsAssembly.Assembly.GetName().Name!);
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.OldMigrationVersionWarning" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
/// <param name="contextType">The <see cref="DbContext" /> type being used.</param>
/// <param name="migrationVersion">The EF Core version the model snapshot was created with.</param>
public static void OldMigrationVersionWarning(
this IDiagnosticsLogger<DbLoggerCategory.Migrations> diagnostics,
Type contextType,
string? migrationVersion)
{
var definition = RelationalResources.LogOldMigrationVersion(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, contextType.ShortDisplayName(), migrationVersion ?? "(unknown)");
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new MigrationVersionEventData(
definition,
OldMigrationVersionWarning,
contextType,
migrationVersion);

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

private static string OldMigrationVersionWarning(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string, string>)definition;
var p = (MigrationVersionEventData)payload;
return d.GenerateMessage(p.ContextType.ShortDisplayName(), p.MigrationVersion ?? "(unknown)");
}

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

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

/// <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
36 changes: 33 additions & 3 deletions src/EFCore.Relational/Migrations/Internal/Migrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -377,8 +377,8 @@ private void ValidateMigrations(bool useTransaction, string? targetMigration)
_logger.ModelSnapshotNotFound(this, _migrationsAssembly);
}
else if (targetMigration == null
&& RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
&& RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
{
var modelSource = (ModelSource)_currentContext.Context.GetService<IModelSource>();
#pragma warning disable EF1001 // Internal EF Core API usage.
Expand All @@ -391,7 +391,24 @@ private void ValidateMigrations(bool useTransaction, string? targetMigration)
}
else
{
_logger.PendingModelChangesWarning(_currentContext.Context.GetType());
var snapshotVersion = _migrationsAssembly.ModelSnapshot.Model.GetProductVersion();
var currentVersion = ProductInfo.GetVersion();

// When the snapshot was generated with an older major version, emit
// OldMigrationVersionWarning instead of PendingModelChangesWarning because
// the detected changes may be caused by snapshot-generation improvements
// in the newer EF Core version rather than actual model changes.
if (snapshotVersion != null
&& TryGetMajorVersion(currentVersion, out var currentMajorVersion)
&& TryGetMajorVersion(snapshotVersion, out var snapshotMajorVersion)
&& snapshotMajorVersion < currentMajorVersion)
{
_logger.OldMigrationVersionWarning(_currentContext.Context.GetType(), snapshotVersion);
}
else
{
_logger.PendingModelChangesWarning(_currentContext.Context.GetType());
}
}
}

Expand All @@ -401,6 +418,19 @@ private void ValidateMigrations(bool useTransaction, string? targetMigration)
}

_logger.MigrateUsingConnection(this, _connection);

static bool TryGetMajorVersion(string? version, out int majorVersion)
{
majorVersion = default;
if (string.IsNullOrEmpty(version))
{
return false;
}

var separatorIndex = version.IndexOf('.');
return separatorIndex > 0
&& int.TryParse(version.AsSpan(0, separatorIndex), out majorVersion);
}
}

private IEnumerable<(string, Func<IReadOnlyList<MigrationCommand>>)> GetMigrationCommandLists(MigratorData parameters)
Expand Down
25 changes: 25 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,10 @@
<value>Model snapshot was not found in assembly '{migrationsAssembly}'. Skipping pending model changes check.</value>
<comment>Information RelationalEventId.ModelSnapshotNotFound string</comment>
</data>
<data name="LogOldMigrationVersion" xml:space="preserve">
<value>Pending model changes were detected for context '{contextType}', but the model snapshot was created with EF Core version '{efVersion}'. These changes may be caused by improvements in snapshot generation in newer versions of EF Core. Consider adding an empty migration to regenerate the snapshot.</value>
<comment>Warning RelationalEventId.OldMigrationVersionWarning string string</comment>
</data>
<data name="LogNonDeterministicModel" xml:space="preserve">
<value>The model for context '{contextType}' changes each time it is built. This is usually caused by dynamic values used in a 'HasData' call (e.g. `new DateTime()`, `Guid.NewGuid()`). Add a new migration and examine its contents to locate the cause, and replace the dynamic call with a static, hardcoded value. See https://aka.ms/efcore-docs-pending-changes.</value>
<comment>Error RelationalEventId.PendingModelChangesWarning string</comment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build
.Log(RelationalEventId.PendingModelChangesWarning)
.Log(RelationalEventId.NonTransactionalMigrationOperationWarning)
.Log(RelationalEventId.MigrationsUserTransactionWarning)
.Log(RelationalEventId.OldMigrationVersionWarning)
);

protected override bool ShouldLogCategory(string logCategory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,80 +764,82 @@ public async Task Throws_when_no_snapshot_async()
}

[ConditionalFact]
public void Throws_for_nondeterministic_HasData()
public void Throws_for_old_migration_version()
{
using var context = new BloggingContext(
Fixture.TestStore.AddProviderOptions(
new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options,
randomData: true);
new DbContextOptionsBuilder().EnableServiceProviderCaching(false)
.ConfigureWarnings(e => e.Throw(RelationalEventId.OldMigrationVersionWarning))).Options,
randomData: false);

context.Database.EnsureDeleted();
GiveMeSomeTime(context);

Assert.Equal(
CoreStrings.WarningAsErrorTemplate(
RelationalEventId.PendingModelChangesWarning.ToString(),
RelationalResources.LogNonDeterministicModel(new TestLogger<TestRelationalLoggingDefinitions>())
.GenerateMessage(nameof(BloggingContext)),
"RelationalEventId.PendingModelChangesWarning"),
RelationalEventId.OldMigrationVersionWarning.ToString(),
RelationalResources.LogOldMigrationVersion(new TestLogger<TestRelationalLoggingDefinitions>())
.GenerateMessage(nameof(BloggingContext), "9.0.0"),
"RelationalEventId.OldMigrationVersionWarning"),
(Assert.Throws<InvalidOperationException>(context.Database.Migrate)).Message);
}

[ConditionalFact]
public async Task Throws_for_nondeterministic_HasData_async()
public async Task Throws_for_old_migration_version_async()
{
using var context = new BloggingContext(
Fixture.TestStore.AddProviderOptions(
new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options,
randomData: true);
new DbContextOptionsBuilder().EnableServiceProviderCaching(false)
.ConfigureWarnings(e => e.Throw(RelationalEventId.OldMigrationVersionWarning))).Options,
randomData: false);

await context.Database.EnsureDeletedAsync();
await GiveMeSomeTimeAsync(context);

Assert.Equal(
CoreStrings.WarningAsErrorTemplate(
RelationalEventId.PendingModelChangesWarning.ToString(),
RelationalResources.LogNonDeterministicModel(new TestLogger<TestRelationalLoggingDefinitions>())
.GenerateMessage(nameof(BloggingContext)),
"RelationalEventId.PendingModelChangesWarning"),
RelationalEventId.OldMigrationVersionWarning.ToString(),
RelationalResources.LogOldMigrationVersion(new TestLogger<TestRelationalLoggingDefinitions>())
.GenerateMessage(nameof(BloggingContext), "9.0.0"),
"RelationalEventId.OldMigrationVersionWarning"),
(await Assert.ThrowsAsync<InvalidOperationException>(() => context.Database.MigrateAsync())).Message);
}

[ConditionalFact]
public void Throws_for_pending_model_changes()
public void Throws_for_nondeterministic_HasData()
{
using var context = new BloggingContext(
Fixture.TestStore.AddProviderOptions(
new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options,
randomData: false);
randomData: true);

context.Database.EnsureDeleted();
GiveMeSomeTime(context);

Assert.Equal(
CoreStrings.WarningAsErrorTemplate(
RelationalEventId.PendingModelChangesWarning.ToString(),
RelationalResources.LogPendingModelChanges(new TestLogger<TestRelationalLoggingDefinitions>())
RelationalResources.LogNonDeterministicModel(new TestLogger<TestRelationalLoggingDefinitions>())
.GenerateMessage(nameof(BloggingContext)),
"RelationalEventId.PendingModelChangesWarning"),
(Assert.Throws<InvalidOperationException>(context.Database.Migrate)).Message);
}

[ConditionalFact]
public async Task Throws_for_pending_model_changes_async()
public async Task Throws_for_nondeterministic_HasData_async()
{
using var context = new BloggingContext(
Fixture.TestStore.AddProviderOptions(
new DbContextOptionsBuilder().EnableServiceProviderCaching(false)).Options,
randomData: false);
randomData: true);

await context.Database.EnsureDeletedAsync();
await GiveMeSomeTimeAsync(context);

Assert.Equal(
CoreStrings.WarningAsErrorTemplate(
RelationalEventId.PendingModelChangesWarning.ToString(),
RelationalResources.LogPendingModelChanges(new TestLogger<TestRelationalLoggingDefinitions>())
RelationalResources.LogNonDeterministicModel(new TestLogger<TestRelationalLoggingDefinitions>())
.GenerateMessage(nameof(BloggingContext)),
"RelationalEventId.PendingModelChangesWarning"),
(await Assert.ThrowsAsync<InvalidOperationException>(() => context.Database.MigrateAsync())).Message);
Expand Down
Loading