diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 77f1aa71345..c9333c2ce91 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -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); @@ -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)})"; diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index e558a9823cf..cea08eaa47d 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -196,6 +196,19 @@ private static IEnumerable 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()) @@ -203,6 +216,16 @@ private static IEnumerable GetAnnotatables(IModel model) 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; @@ -212,6 +235,45 @@ private static IEnumerable 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 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; + } } } diff --git a/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs b/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs index 083cdb4daea..602ccaf7e96 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsScaffolder.cs @@ -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 @@ -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, diff --git a/src/EFCore.Design/Migrations/Internal/ISnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/ISnapshotModelProcessor.cs index 10db89142f6..a4458697859 100644 --- a/src/EFCore.Design/Migrations/Internal/ISnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/ISnapshotModelProcessor.cs @@ -20,5 +20,5 @@ public interface ISnapshotModelProcessor /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [return: NotNullIfNotNull("model")] - IModel? Process(IReadOnlyModel? model); + IModel? Process(IReadOnlyModel? model, bool resetVersion = false); } diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index d5beace4bb6..788e4cf32ca 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -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. /// - public virtual IModel? Process(IReadOnlyModel? model) + public virtual IModel? Process(IReadOnlyModel? model, bool resetVersion = false) { if (model == null) { @@ -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); } @@ -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) diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index eda02e60359..30b97be4e97 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Globalization; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Update.Internal; diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index f4575760a80..23cb3dedafb 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -597,7 +597,18 @@ public static IEnumerable 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) { diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs index f59983973bc..99ba61828c4 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs @@ -1062,7 +1062,6 @@ public virtual void Non_base_abstract_base_class_with_TPC() }, """ // -using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -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)); @@ -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)); @@ -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); @@ -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] @@ -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] diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index ab6da06f625..3eacf985050 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -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(); diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index c6b08ad63b3..eddc5e443cc 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -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(); - var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType); - var reporter = new TestOperationReporter(); - var modelRuntimeInitializer = - SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); - var processor = new SnapshotModelProcessor(reporter, modelRuntimeInitializer); - var model = processor.Process(snapshot.Model); - - var differences = differ.GetDifferences( - model.GetRelationalModel(), - context.GetService().Model.GetRelationalModel()); - - Assert.Empty(differences); + AssertSameSnapshot(snapshotType, context); } [ConditionalTheory] @@ -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(); var snapshot = (ModelSnapshot)Activator.CreateInstance(snapshotType); var reporter = new TestOperationReporter(); - var setBuilder = SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); - var processor = new SnapshotModelProcessor(reporter, setBuilder); - var model = processor.Process(snapshot.Model); + var modelRuntimeInitializer = + SqlServerTestHelpers.Instance.CreateContextServices().GetRequiredService(); + + var model = PreprocessModel(snapshot); + model = new SnapshotModelProcessor(reporter, modelRuntimeInitializer).Process(model, resetVersion: true); + var currentModel = context.GetService().Model; var differences = differ.GetDifferences( - model.GetRelationalModel(), context.GetService().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)