Skip to content

Commit

Permalink
[release/9.0] Add more specific messages when pending model changes a…
Browse files Browse the repository at this point in the history
…re detected (#35221)

Fixes #35133
  • Loading branch information
AndriySvyryd authored Dec 2, 2024
1 parent d89ec3f commit 507152b
Show file tree
Hide file tree
Showing 10 changed files with 424 additions and 65 deletions.
14 changes: 14 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private enum Id
NonTransactionalMigrationOperationWarning,
AcquiringMigrationLock,
MigrationsUserTransactionWarning,
ModelSnapshotNotFound,

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

/// <summary>
/// Model snapshot was not found.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Migrations" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="MigrationAssemblyEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId ModelSnapshotNotFound = MakeMigrationsId(Id.ModelSnapshotNotFound);

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

private static EventId MakeQueryId(Id id)
Expand Down
71 changes: 71 additions & 0 deletions src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2343,6 +2343,77 @@ private static string PendingModelChanges(EventDefinitionBase definition, EventD
return d.GenerateMessage(p.ContextType.ShortDisplayName());
}

/// <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 NonDeterministicModel(
this IDiagnosticsLogger<DbLoggerCategory.Migrations> diagnostics,
Type contextType)
{
var definition = RelationalResources.LogNonDeterministicModel(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,
NonDeterministicModel,
contextType);

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

private static string NonDeterministicModel(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.MigrationsNotFound" /> event.
/// </summary>
/// <param name="diagnostics">The diagnostics logger to use.</param>
/// <param name="migrator">The migrator.</param>
/// <param name="migrationsAssembly">The assembly in which migrations are stored.</param>
public static void ModelSnapshotNotFound(
this IDiagnosticsLogger<DbLoggerCategory.Migrations> diagnostics,
IMigrator migrator,
IMigrationsAssembly migrationsAssembly)
{
var definition = RelationalResources.LogNoModelSnapshotFound(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, migrationsAssembly.Assembly.GetName().Name!);
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new MigrationAssemblyEventData(
definition,
ModelSnapshotNotFound,
migrator,
migrationsAssembly);

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

private static string ModelSnapshotNotFound(EventDefinitionBase definition, EventData payload)
{
var d = (EventDefinition<string>)definition;
var p = (MigrationAssemblyEventData)payload;
return d.GenerateMessage(p.MigrationsAssembly.Assembly.GetName().Name!);
}

/// <summary>
/// Logs for the <see cref="RelationalEventId.NonTransactionalMigrationOperationWarning" /> event.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[EntityFrameworkInternal]
public EventDefinitionBase? LogMigrationsUserTransactionWarning;
public EventDefinitionBase? LogMigrationsUserTransaction;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -673,6 +673,24 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
[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? LogNonDeterministicModel;

/// <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? 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
Expand Down
85 changes: 45 additions & 40 deletions src/EFCore.Relational/Migrations/Internal/Migrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Transactions;
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;

namespace Microsoft.EntityFrameworkCore.Migrations.Internal;

Expand Down Expand Up @@ -93,24 +94,7 @@ public Migrator(
public virtual void Migrate(string? targetMigration)
{
var useTransaction = _connection.CurrentTransaction is null;
if (!useTransaction
&& _executionStrategy.RetriesOnFailure)
{
throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction);
}

if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
{
_logger.PendingModelChangesWarning(_currentContext.Context.GetType());
}

if (!useTransaction)
{
_logger.MigrationsUserTransactionWarning();
}

_logger.MigrateUsingConnection(this, _connection);
ValidateMigrations(useTransaction);

using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);

Expand Down Expand Up @@ -235,24 +219,7 @@ public virtual async Task MigrateAsync(
CancellationToken cancellationToken = default)
{
var useTransaction = _connection.CurrentTransaction is null;
if (!useTransaction
&& _executionStrategy.RetriesOnFailure)
{
throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction);
}

if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
{
_logger.PendingModelChangesWarning(_currentContext.Context.GetType());
}

if (!useTransaction)
{
_logger.MigrationsUserTransactionWarning();
}

_logger.MigrateUsingConnection(this, _connection);
ValidateMigrations(useTransaction);

using var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled);

Expand Down Expand Up @@ -382,6 +349,48 @@ await _migrationCommandExecutor.ExecuteNonQueryAsync(
}
}

private void ValidateMigrations(bool useTransaction)
{
if (!useTransaction
&& _executionStrategy.RetriesOnFailure)
{
throw new NotSupportedException(RelationalStrings.TransactionSuppressedMigrationInUserTransaction);
}

if (_migrationsAssembly.Migrations.Count == 0)
{
_logger.MigrationsNotFound(this, _migrationsAssembly);
}
else if (_migrationsAssembly.ModelSnapshot == null)
{
_logger.ModelSnapshotNotFound(this, _migrationsAssembly);
}
else if (RelationalResources.LogPendingModelChanges(_logger).WarningBehavior != WarningBehavior.Ignore
&& HasPendingModelChanges())
{
var modelSource = (ModelSource)_currentContext.Context.GetService<IModelSource>();
#pragma warning disable EF1001 // Internal EF Core API usage.
var newDesignTimeModel = modelSource.CreateModel(
_currentContext.Context, _currentContext.Context.GetService<ModelCreationDependencies>(), designTime: true);
#pragma warning restore EF1001 // Internal EF Core API usage.
if (_migrationsModelDiffer.HasDifferences(newDesignTimeModel.GetRelationalModel(), _designTimeModel.Model.GetRelationalModel()))
{
_logger.NonDeterministicModel(_currentContext.Context.GetType());
}
else
{
_logger.PendingModelChangesWarning(_currentContext.Context.GetType());
}
}

if (!useTransaction)
{
_logger.MigrationsUserTransactionWarning();
}

_logger.MigrateUsingConnection(this, _connection);
}

private IEnumerable<(string, Func<IReadOnlyList<MigrationCommand>>)> GetMigrationCommandLists(MigratorData parameters)
{
var migrationsToApply = parameters.AppliedMigrations;
Expand Down Expand Up @@ -449,10 +458,6 @@ protected virtual void PopulateMigrations(
var appliedMigrations = new Dictionary<string, TypeInfo>();
var unappliedMigrations = new Dictionary<string, TypeInfo>();
var appliedMigrationEntrySet = new HashSet<string>(appliedMigrationEntries, StringComparer.OrdinalIgnoreCase);
if (_migrationsAssembly.Migrations.Count == 0)
{
_logger.MigrationsNotFound(this, _migrationsAssembly);
}

foreach (var (key, typeInfo) in _migrationsAssembly.Migrations)
{
Expand Down
62 changes: 56 additions & 6 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.

Loading

0 comments on commit 507152b

Please sign in to comment.