Skip to content

Commit

Permalink
Upgrade the snapshot when rolling back to a migration from a previous…
Browse files Browse the repository at this point in the history
… version

Fixes #32555
  • Loading branch information
AndriySvyryd committed Dec 29, 2023
1 parent 040fe9a commit 1f80a97
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 37 deletions.
17 changes: 11 additions & 6 deletions src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,18 @@ protected virtual void GenerateEntityType(
{
var ownership = entityType.FindOwnership();
var ownerNavigation = ownership?.PrincipalToDependent!.Name;

var entityTypeName = entityType.Name;
if (ownerNavigation != null
&& entityType.HasSharedClrType
&& entityTypeName == ownership!.PrincipalEntityType.GetOwnedName(entityType.ClrType.ShortDisplayName(), ownerNavigation))
&& entityType.HasSharedClrType)
{
entityTypeName = entityType.ClrType.DisplayName();
if (entityTypeName == ownership!.PrincipalEntityType.GetOwnedName(entityType.ClrType.ShortDisplayName(), ownerNavigation))
{
entityTypeName = entityType.ClrType.DisplayName();
}
else if (entityTypeName == ownership!.PrincipalEntityType.GetOwnedName(entityType.ShortName(), ownerNavigation))
{
entityTypeName = entityType.ShortName();
}
}

var entityTypeBuilderName = GenerateNestedBuilderName(builderName);
Expand Down Expand Up @@ -436,8 +441,8 @@ protected virtual void GenerateProperty(
IProperty property,
IndentedStringBuilder stringBuilder)
{
var clrType = FindValueConverter(property)?.ProviderClrType.MakeNullable(property.IsNullable)
?? property.ClrType;
var clrType = (FindValueConverter(property)?.ProviderClrType ?? property.ClrType)
.MakeNullable(property.IsNullable);

var propertyBuilderName = $"{entityTypeBuilderName}.Property<{Code.Reference(clrType)}>({Code.Literal(property.Name)})";

Expand Down
62 changes: 62 additions & 0 deletions src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,36 @@ private static IEnumerable<IAnnotatable> GetAnnotatables(IModel model)
foreach (var property in entityType.GetDeclaredProperties())
{
yield return property;

foreach (var @override in property.GetOverrides())
{
yield return @override;
}
}

foreach (var property in entityType.GetDeclaredComplexProperties())
{
foreach (var annotatable in GetAnnotatables(property))
{
yield return annotatable;
}
}

foreach (var key in entityType.GetDeclaredKeys())
{
yield return key;
}

foreach (var navigation in entityType.GetDeclaredNavigations())
{
yield return navigation;
}

foreach (var navigation in entityType.GetDeclaredSkipNavigations())
{
yield return navigation;
}

foreach (var foreignKey in entityType.GetDeclaredForeignKeys())
{
yield return foreignKey;
Expand All @@ -212,6 +235,45 @@ private static IEnumerable<IAnnotatable> GetAnnotatables(IModel model)
{
yield return index;
}

foreach (var checkConstraint in entityType.GetDeclaredCheckConstraints())
{
yield return checkConstraint;
}

foreach (var trigger in entityType.GetDeclaredTriggers())
{
yield return trigger;
}

foreach (var fragment in entityType.GetMappingFragments())
{
yield return fragment;
}
}

foreach (var sequence in model.GetSequences())
{
yield return sequence;
}
}

private static IEnumerable<IAnnotatable> GetAnnotatables(IComplexProperty complexProperty)
{
yield return complexProperty;
yield return complexProperty.ComplexType;

foreach (var property in complexProperty.ComplexType.GetDeclaredProperties())
{
yield return property;
}

foreach (var property in complexProperty.ComplexType.GetDeclaredComplexProperties())
{
foreach (var annotatable in GetAnnotatables(property))
{
yield return annotatable;
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ public virtual MigrationFiles RemoveMigration(
}

model = migrations.Count > 1
? Dependencies.SnapshotModelProcessor.Process(migrations[^2].TargetModel)
? Dependencies.SnapshotModelProcessor.Process(migrations[^2].TargetModel, resetVersion: true)
: null;
}
else
Expand Down Expand Up @@ -351,6 +351,7 @@ public virtual MigrationFiles RemoveMigration(
{
var modelSnapshotNamespace = modelSnapshot.GetType().Namespace;
Check.DebugAssert(!string.IsNullOrEmpty(modelSnapshotNamespace), "modelSnapshotNamespace is null or empty");

var modelSnapshotCode = codeGenerator.GenerateSnapshot(
modelSnapshotNamespace,
_contextType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ public interface ISnapshotModelProcessor
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
[return: NotNullIfNotNull("model")]
IModel? Process(IReadOnlyModel? model);
IModel? Process(IReadOnlyModel? model, bool resetVersion = false);
}
19 changes: 16 additions & 3 deletions src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public SnapshotModelProcessor(
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IModel? Process(IReadOnlyModel? model)
public virtual IModel? Process(IReadOnlyModel? model, bool resetVersion = false)
{
if (model == null)
{
Expand Down Expand Up @@ -76,6 +76,15 @@ public SnapshotModelProcessor(
}
}

if (model is IMutableModel mutableModel)
{
mutableModel.RemoveAnnotation("ChangeDetector.SkipDetectChanges");
if (resetVersion)
{
mutableModel.SetProductVersion(ProductInfo.GetVersion());
}
}

return _modelRuntimeInitializer.Initialize((IModel)model, designTime: true, validationLogger: null);
}

Expand Down Expand Up @@ -145,13 +154,17 @@ private static void UpdateSequences(IReadOnlyModel model, string version)
var sequences = model.GetAnnotations()
#pragma warning disable CS0618 // Type or member is obsolete
.Where(a => a.Name.StartsWith(RelationalAnnotationNames.SequencePrefix, StringComparison.Ordinal))
.Select(a => new Sequence(model, a.Name));
.ToList();
#pragma warning restore CS0618 // Type or member is obsolete

var sequencesDictionary = new Dictionary<(string, string?), ISequence>();
foreach (var sequence in sequences)
foreach (var sequenceAnnotation in sequences)
{
#pragma warning disable CS0618 // Type or member is obsolete
var sequence = new Sequence(model, sequenceAnnotation.Name);
#pragma warning restore CS0618 // Type or member is obsolete
sequencesDictionary[(sequence.Name, sequence.ModelSchema)] = sequence;
mutableModel.RemoveAnnotation(sequenceAnnotation.Name);
}

if (sequencesDictionary.Count > 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections;
using System.Globalization;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Update.Internal;

Expand Down
13 changes: 12 additions & 1 deletion src/Shared/SharedTypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,18 @@ public static IEnumerable<string> GetNamespaces(this Type type)
yield break;
}

yield return type.Namespace!;
if (type.IsConstructedGenericType
&& type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
foreach (var ns in type.UnwrapNullableType().GetNamespaces())
{
yield return ns;
}
}
else
{
yield return type.Namespace!;
}

if (type.IsGenericType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1062,7 +1062,6 @@ public virtual void Non_base_abstract_base_class_with_TPC()
},
"""
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
Expand Down Expand Up @@ -3557,7 +3556,7 @@ public virtual void Owned_types_are_stored_in_snapshot()
b.Navigation("Properties");
});
""", usingSystem: true),
"""),
o =>
{
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
Expand Down Expand Up @@ -3793,7 +3792,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded()
b.Navigation("Properties");
});
""", usingSystem: true),
"""),
o =>
{
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
Expand Down Expand Up @@ -4881,7 +4880,7 @@ public virtual void Property_column_name_is_stored_in_snapshot_when_DefaultColum
{
b.Navigation("Bar");
});
""", usingSystem: true),
"""),
model =>
{
var entityType = model.FindEntityType(typeof(BarA).FullName);
Expand Down Expand Up @@ -5345,7 +5344,7 @@ public virtual void Property_of_enum_to_nullable()
b.ToTable("EntityWithEnumType", "DefaultSchema");
});
""", usingSystem: true),
"""),
o => Assert.False(o.GetEntityTypes().First().FindProperty("Day").IsNullable));

[ConditionalFact]
Expand Down Expand Up @@ -7214,7 +7213,7 @@ public virtual void Do_not_generate_entity_type_builder_again_if_no_foreign_key_
b.Navigation("Navigation");
});
""", usingSystem: true),
"""),
o => { });

[ConditionalFact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ public void Multidimensional_array_warning_is_not_suppressed_for_unidimensional_
Assert.DoesNotContain("#pragma warning disable CA1814", migration);
}

private static IMigrationsCodeGenerator CreateMigrationsCodeGenerator()
public static IMigrationsCodeGenerator CreateMigrationsCodeGenerator()
{
var testAssembly = typeof(CSharpMigrationsGeneratorTest).Assembly;
var reporter = new TestOperationReporter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,7 @@ public void Sets_owned_type_keys()
public void Can_diff_against_older_ownership_model(Type snapshotType)
{
using var context = new OwnershipContext();
var differ = context.GetService<IMigrationsModelDiffer>();
var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType);
var reporter = new TestOperationReporter();
var modelRuntimeInitializer =
SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService<IModelRuntimeInitializer>();
var processor = new SnapshotModelProcessor(reporter, modelRuntimeInitializer);
var model = processor.Process(snapshot.Model);

var differences = differ.GetDifferences(
model.GetRelationalModel(),
context.GetService<IDesignTimeModel>().Model.GetRelationalModel());

Assert.Empty(differences);
AssertSameSnapshot(snapshotType, context);
}

[ConditionalTheory]
Expand All @@ -213,17 +201,84 @@ public void Can_diff_against_older_ownership_model(Type snapshotType)
public void Can_diff_against_older_sequence_model(Type snapshotType)
{
using var context = new SequenceContext();
AssertSameSnapshot(snapshotType, context);
}

private static void AssertSameSnapshot(Type snapshotType, DbContext context)
{
var differ = context.GetService<IMigrationsModelDiffer>();
var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType);
var reporter = new TestOperationReporter();
var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService<IModelRuntimeInitializer>();
var processor = new SnapshotModelProcessor(reporter, setBuilder);
var model = processor.Process(snapshot.Model);
var modelRuntimeInitializer =
SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService<IModelRuntimeInitializer>();

var model = PreprocessModel(snapshot);
model = new SnapshotModelProcessor(reporter, modelRuntimeInitializer).Process(model, resetVersion: true);
var currentModel = context.GetService<IDesignTimeModel>().Model;

var differences = differ.GetDifferences(
model.GetRelationalModel(), context.GetService<IDesignTimeModel>().Model.GetRelationalModel());
model.GetRelationalModel(),
currentModel.GetRelationalModel());

Assert.Empty(differences);

var generator = CSharpMigrationsGeneratorTest.CreateMigrationsCodeGenerator();

var oldSnapshotCode = generator.GenerateSnapshot(
"MyNamespace",
context.GetType(),
"MySnapshot",
model);

var newSnapshotCode = generator.GenerateSnapshot(
"MyNamespace",
context.GetType(),
"MySnapshot",
currentModel);

Assert.Equal(newSnapshotCode, oldSnapshotCode);
}

private static IModel PreprocessModel(ModelSnapshot snapshot)
{
var model = snapshot.Model;
if (model.FindAnnotation(RelationalAnnotationNames.MaxIdentifierLength) == null)
{
((Model)model)[RelationalAnnotationNames.MaxIdentifierLength] = 128;
}

foreach (EntityType entityType in model.GetEntityTypes())
{
var schemaAnnotation = entityType.FindAnnotation(RelationalAnnotationNames.Schema);
if (schemaAnnotation != null
&& schemaAnnotation.Value == null)
{
entityType.RemoveAnnotation(RelationalAnnotationNames.Schema);
}

foreach (var property in entityType.GetProperties())
{
if (property.IsForeignKey())
{
if (property.ValueGenerated != ValueGenerated.Never)
{
property.SetValueGenerated(null, ConfigurationSource.Explicit);
}

if (property.GetValueGenerationStrategy() != SqlServerValueGenerationStrategy.None)
{
property.SetValueGenerationStrategy(null);
}
}
else if (property.GetValueGenerationStrategy() is SqlServerValueGenerationStrategy strategy
&& strategy != SqlServerValueGenerationStrategy.None)
{
property.SetValueGenerationStrategy(strategy);
}
}
}

return model;
}

private void AddAnnotations(IMutableAnnotatable element)
Expand Down

0 comments on commit 1f80a97

Please sign in to comment.