Skip to content

Commit 9fdf1c4

Browse files
authored
Upgrade the snapshot when rolling back to a migration from a previous version (#32690)
Fixes #32555
1 parent bf21ca5 commit 9fdf1c4

10 files changed

+182
-37
lines changed

src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs

+11-6
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,18 @@ protected virtual void GenerateEntityType(
124124
{
125125
var ownership = entityType.FindOwnership();
126126
var ownerNavigation = ownership?.PrincipalToDependent!.Name;
127-
128127
var entityTypeName = entityType.Name;
129128
if (ownerNavigation != null
130-
&& entityType.HasSharedClrType
131-
&& entityTypeName == ownership!.PrincipalEntityType.GetOwnedName(entityType.ClrType.ShortDisplayName(), ownerNavigation))
129+
&& entityType.HasSharedClrType)
132130
{
133-
entityTypeName = entityType.ClrType.DisplayName();
131+
if (entityTypeName == ownership!.PrincipalEntityType.GetOwnedName(entityType.ClrType.ShortDisplayName(), ownerNavigation))
132+
{
133+
entityTypeName = entityType.ClrType.DisplayName();
134+
}
135+
else if (entityTypeName == ownership!.PrincipalEntityType.GetOwnedName(entityType.ShortName(), ownerNavigation))
136+
{
137+
entityTypeName = entityType.ShortName();
138+
}
134139
}
135140

136141
var entityTypeBuilderName = GenerateNestedBuilderName(builderName);
@@ -436,8 +441,8 @@ protected virtual void GenerateProperty(
436441
IProperty property,
437442
IndentedStringBuilder stringBuilder)
438443
{
439-
var clrType = FindValueConverter(property)?.ProviderClrType.MakeNullable(property.IsNullable)
440-
?? property.ClrType;
444+
var clrType = (FindValueConverter(property)?.ProviderClrType ?? property.ClrType)
445+
.MakeNullable(property.IsNullable);
441446

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

src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs

+62
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,36 @@ private static IEnumerable<IAnnotatable> GetAnnotatables(IModel model)
196196
foreach (var property in entityType.GetDeclaredProperties())
197197
{
198198
yield return property;
199+
200+
foreach (var @override in property.GetOverrides())
201+
{
202+
yield return @override;
203+
}
204+
}
205+
206+
foreach (var property in entityType.GetDeclaredComplexProperties())
207+
{
208+
foreach (var annotatable in GetAnnotatables(property))
209+
{
210+
yield return annotatable;
211+
}
199212
}
200213

201214
foreach (var key in entityType.GetDeclaredKeys())
202215
{
203216
yield return key;
204217
}
205218

219+
foreach (var navigation in entityType.GetDeclaredNavigations())
220+
{
221+
yield return navigation;
222+
}
223+
224+
foreach (var navigation in entityType.GetDeclaredSkipNavigations())
225+
{
226+
yield return navigation;
227+
}
228+
206229
foreach (var foreignKey in entityType.GetDeclaredForeignKeys())
207230
{
208231
yield return foreignKey;
@@ -212,6 +235,45 @@ private static IEnumerable<IAnnotatable> GetAnnotatables(IModel model)
212235
{
213236
yield return index;
214237
}
238+
239+
foreach (var checkConstraint in entityType.GetDeclaredCheckConstraints())
240+
{
241+
yield return checkConstraint;
242+
}
243+
244+
foreach (var trigger in entityType.GetDeclaredTriggers())
245+
{
246+
yield return trigger;
247+
}
248+
249+
foreach (var fragment in entityType.GetMappingFragments())
250+
{
251+
yield return fragment;
252+
}
253+
}
254+
255+
foreach (var sequence in model.GetSequences())
256+
{
257+
yield return sequence;
258+
}
259+
}
260+
261+
private static IEnumerable<IAnnotatable> GetAnnotatables(IComplexProperty complexProperty)
262+
{
263+
yield return complexProperty;
264+
yield return complexProperty.ComplexType;
265+
266+
foreach (var property in complexProperty.ComplexType.GetDeclaredProperties())
267+
{
268+
yield return property;
269+
}
270+
271+
foreach (var property in complexProperty.ComplexType.GetDeclaredComplexProperties())
272+
{
273+
foreach (var annotatable in GetAnnotatables(property))
274+
{
275+
yield return annotatable;
276+
}
215277
}
216278
}
217279

src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ public virtual MigrationFiles RemoveMigration(
319319
}
320320

321321
model = migrations.Count > 1
322-
? Dependencies.SnapshotModelProcessor.Process(migrations[^2].TargetModel)
322+
? Dependencies.SnapshotModelProcessor.Process(migrations[^2].TargetModel, resetVersion: true)
323323
: null;
324324
}
325325
else
@@ -351,6 +351,7 @@ public virtual MigrationFiles RemoveMigration(
351351
{
352352
var modelSnapshotNamespace = modelSnapshot.GetType().Namespace;
353353
Check.DebugAssert(!string.IsNullOrEmpty(modelSnapshotNamespace), "modelSnapshotNamespace is null or empty");
354+
354355
var modelSnapshotCode = codeGenerator.GenerateSnapshot(
355356
modelSnapshotNamespace,
356357
_contextType,

src/EFCore.Design/Migrations/Internal/ISnapshotModelProcessor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ public interface ISnapshotModelProcessor
2020
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2121
/// </summary>
2222
[return: NotNullIfNotNull("model")]
23-
IModel? Process(IReadOnlyModel? model);
23+
IModel? Process(IReadOnlyModel? model, bool resetVersion = false);
2424
}

src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs

+16-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public SnapshotModelProcessor(
4747
/// any release. You should only use it directly in your code with extreme caution and knowing that
4848
/// doing so can result in application failures when updating to a new Entity Framework Core release.
4949
/// </summary>
50-
public virtual IModel? Process(IReadOnlyModel? model)
50+
public virtual IModel? Process(IReadOnlyModel? model, bool resetVersion = false)
5151
{
5252
if (model == null)
5353
{
@@ -76,6 +76,15 @@ public SnapshotModelProcessor(
7676
}
7777
}
7878

79+
if (model is IMutableModel mutableModel)
80+
{
81+
mutableModel.RemoveAnnotation("ChangeDetector.SkipDetectChanges");
82+
if (resetVersion)
83+
{
84+
mutableModel.SetProductVersion(ProductInfo.GetVersion());
85+
}
86+
}
87+
7988
return _modelRuntimeInitializer.Initialize((IModel)model, designTime: true, validationLogger: null);
8089
}
8190

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

151160
var sequencesDictionary = new Dictionary<(string, string?), ISequence>();
152-
foreach (var sequence in sequences)
161+
foreach (var sequenceAnnotation in sequences)
153162
{
163+
#pragma warning disable CS0618 // Type or member is obsolete
164+
var sequence = new Sequence(model, sequenceAnnotation.Name);
165+
#pragma warning restore CS0618 // Type or member is obsolete
154166
sequencesDictionary[(sequence.Name, sequence.ModelSchema)] = sequence;
167+
mutableModel.RemoveAnnotation(sequenceAnnotation.Name);
155168
}
156169

157170
if (sequencesDictionary.Count > 0)

src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Collections;
55
using System.Globalization;
6-
using Microsoft.EntityFrameworkCore.Metadata;
76
using Microsoft.EntityFrameworkCore.Metadata.Internal;
87
using Microsoft.EntityFrameworkCore.Update.Internal;
98

src/Shared/SharedTypeExtensions.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,18 @@ public static IEnumerable<string> GetNamespaces(this Type type)
597597
yield break;
598598
}
599599

600-
yield return type.Namespace!;
600+
if (type.IsConstructedGenericType
601+
&& type.GetGenericTypeDefinition() == typeof(Nullable<>))
602+
{
603+
foreach (var ns in type.UnwrapNullableType().GetNamespaces())
604+
{
605+
yield return ns;
606+
}
607+
}
608+
else
609+
{
610+
yield return type.Namespace!;
611+
}
601612

602613
if (type.IsGenericType)
603614
{

test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,6 @@ public virtual void Non_base_abstract_base_class_with_TPC()
10621062
},
10631063
"""
10641064
// <auto-generated />
1065-
using System;
10661065
using Microsoft.EntityFrameworkCore;
10671066
using Microsoft.EntityFrameworkCore.Infrastructure;
10681067
using Microsoft.EntityFrameworkCore.Metadata;
@@ -3557,7 +3556,7 @@ public virtual void Owned_types_are_stored_in_snapshot()
35573556
35583557
b.Navigation("Properties");
35593558
});
3560-
""", usingSystem: true),
3559+
"""),
35613560
o =>
35623561
{
35633562
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
@@ -3793,7 +3792,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded()
37933792
37943793
b.Navigation("Properties");
37953794
});
3796-
""", usingSystem: true),
3795+
"""),
37973796
o =>
37983797
{
37993798
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
@@ -4881,7 +4880,7 @@ public virtual void Property_column_name_is_stored_in_snapshot_when_DefaultColum
48814880
{
48824881
b.Navigation("Bar");
48834882
});
4884-
""", usingSystem: true),
4883+
"""),
48854884
model =>
48864885
{
48874886
var entityType = model.FindEntityType(typeof(BarA).FullName);
@@ -5345,7 +5344,7 @@ public virtual void Property_of_enum_to_nullable()
53455344
53465345
b.ToTable("EntityWithEnumType", "DefaultSchema");
53475346
});
5348-
""", usingSystem: true),
5347+
"""),
53495348
o => Assert.False(o.GetEntityTypes().First().FindProperty("Day").IsNullable));
53505349

53515350
[ConditionalFact]
@@ -7214,7 +7213,7 @@ public virtual void Do_not_generate_entity_type_builder_again_if_no_foreign_key_
72147213
72157214
b.Navigation("Navigation");
72167215
});
7217-
""", usingSystem: true),
7216+
"""),
72187217
o => { });
72197218

72207219
[ConditionalFact]

test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ public void Multidimensional_array_warning_is_not_suppressed_for_unidimensional_
905905
Assert.DoesNotContain("#pragma warning disable CA1814", migration);
906906
}
907907

908-
private static IMigrationsCodeGenerator CreateMigrationsCodeGenerator()
908+
public static IMigrationsCodeGenerator CreateMigrationsCodeGenerator()
909909
{
910910
var testAssembly = typeof(CSharpMigrationsGeneratorTest).Assembly;
911911
var reporter = new TestOperationReporter();

test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs

+72-17
Original file line numberDiff line numberDiff line change
@@ -191,19 +191,7 @@ public void Sets_owned_type_keys()
191191
public void Can_diff_against_older_ownership_model(Type snapshotType)
192192
{
193193
using var context = new OwnershipContext();
194-
var differ = context.GetService<IMigrationsModelDiffer>();
195-
var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType);
196-
var reporter = new TestOperationReporter();
197-
var modelRuntimeInitializer =
198-
SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService<IModelRuntimeInitializer>();
199-
var processor = new SnapshotModelProcessor(reporter, modelRuntimeInitializer);
200-
var model = processor.Process(snapshot.Model);
201-
202-
var differences = differ.GetDifferences(
203-
model.GetRelationalModel(),
204-
context.GetService<IDesignTimeModel>().Model.GetRelationalModel());
205-
206-
Assert.Empty(differences);
194+
AssertSameSnapshot(snapshotType, context);
207195
}
208196

209197
[ConditionalTheory]
@@ -213,17 +201,84 @@ public void Can_diff_against_older_ownership_model(Type snapshotType)
213201
public void Can_diff_against_older_sequence_model(Type snapshotType)
214202
{
215203
using var context = new SequenceContext();
204+
AssertSameSnapshot(snapshotType, context);
205+
}
206+
207+
private static void AssertSameSnapshot(Type snapshotType, DbContext context)
208+
{
216209
var differ = context.GetService<IMigrationsModelDiffer>();
217210
var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType);
218211
var reporter = new TestOperationReporter();
219-
var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService<IModelRuntimeInitializer>();
220-
var processor = new SnapshotModelProcessor(reporter, setBuilder);
221-
var model = processor.Process(snapshot.Model);
212+
var modelRuntimeInitializer =
213+
SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService<IModelRuntimeInitializer>();
214+
215+
var model = PreprocessModel(snapshot);
216+
model = new SnapshotModelProcessor(reporter, modelRuntimeInitializer).Process(model, resetVersion: true);
217+
var currentModel = context.GetService<IDesignTimeModel>().Model;
222218

223219
var differences = differ.GetDifferences(
224-
model.GetRelationalModel(), context.GetService<IDesignTimeModel>().Model.GetRelationalModel());
220+
model.GetRelationalModel(),
221+
currentModel.GetRelationalModel());
225222

226223
Assert.Empty(differences);
224+
225+
var generator = CSharpMigrationsGeneratorTest.CreateMigrationsCodeGenerator();
226+
227+
var oldSnapshotCode = generator.GenerateSnapshot(
228+
"MyNamespace",
229+
context.GetType(),
230+
"MySnapshot",
231+
model);
232+
233+
var newSnapshotCode = generator.GenerateSnapshot(
234+
"MyNamespace",
235+
context.GetType(),
236+
"MySnapshot",
237+
currentModel);
238+
239+
Assert.Equal(newSnapshotCode, oldSnapshotCode);
240+
}
241+
242+
private static IModel PreprocessModel(ModelSnapshot snapshot)
243+
{
244+
var model = snapshot.Model;
245+
if (model.FindAnnotation(RelationalAnnotationNames.MaxIdentifierLength) == null)
246+
{
247+
((Model)model)[RelationalAnnotationNames.MaxIdentifierLength] = 128;
248+
}
249+
250+
foreach (EntityType entityType in model.GetEntityTypes())
251+
{
252+
var schemaAnnotation = entityType.FindAnnotation(RelationalAnnotationNames.Schema);
253+
if (schemaAnnotation != null
254+
&& schemaAnnotation.Value == null)
255+
{
256+
entityType.RemoveAnnotation(RelationalAnnotationNames.Schema);
257+
}
258+
259+
foreach (var property in entityType.GetProperties())
260+
{
261+
if (property.IsForeignKey())
262+
{
263+
if (property.ValueGenerated != ValueGenerated.Never)
264+
{
265+
property.SetValueGenerated(null, ConfigurationSource.Explicit);
266+
}
267+
268+
if (property.GetValueGenerationStrategy() != SqlServerValueGenerationStrategy.None)
269+
{
270+
property.SetValueGenerationStrategy(null);
271+
}
272+
}
273+
else if (property.GetValueGenerationStrategy() is SqlServerValueGenerationStrategy strategy
274+
&& strategy != SqlServerValueGenerationStrategy.None)
275+
{
276+
property.SetValueGenerationStrategy(strategy);
277+
}
278+
}
279+
}
280+
281+
return model;
227282
}
228283

229284
private void AddAnnotations(IMutableAnnotatable element)

0 commit comments

Comments
 (0)