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

Upgrade the snapshot when rolling back to a migration from a previous version #32690

Merged
merged 1 commit into from
Jan 4, 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
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
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
Loading