Skip to content

Commit ab2f0a6

Browse files
authored
Fix bug where annotations added by initial migration are not removed when migration is reverted. (#3714)
Adds NpgsqlMigrationsAnnotationProvider to implement ForRemove(IRelationalModel). Fixes #3604
1 parent 98d0671 commit ab2f0a6

5 files changed

Lines changed: 96 additions & 6 deletions

File tree

src/EFCore.PG/Extensions/NpgsqlServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public static IServiceCollection AddEntityFrameworkNpgsql(this IServiceCollectio
9494
.TryAdd<IRelationalTypeMappingSource, NpgsqlTypeMappingSource>()
9595
.TryAdd<ISqlGenerationHelper, NpgsqlSqlGenerationHelper>()
9696
.TryAdd<IRelationalAnnotationProvider, NpgsqlAnnotationProvider>()
97+
.TryAdd<IMigrationsAnnotationProvider, NpgsqlMigrationsAnnotationProvider>()
9798
.TryAdd<IModelValidator, NpgsqlModelValidator>()
9899
.TryAdd<IMigrator, NpgsqlMigrator>()
99100
.TryAdd<IProviderConventionSetBuilder, NpgsqlConventionSetBuilder>()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal;
2+
3+
internal static class NpgsqlAnnotationHelper
4+
{
5+
internal static bool IsRelationalModelAnnotation(IAnnotation annotation)
6+
=> annotation.Name.StartsWith(NpgsqlAnnotationNames.PostgresExtensionPrefix, StringComparison.Ordinal)
7+
|| annotation.Name.StartsWith(NpgsqlAnnotationNames.EnumPrefix, StringComparison.Ordinal)
8+
|| annotation.Name.StartsWith(NpgsqlAnnotationNames.RangePrefix, StringComparison.Ordinal)
9+
|| annotation.Name.StartsWith(NpgsqlAnnotationNames.CollationDefinitionPrefix, StringComparison.Ordinal);
10+
}

src/EFCore.PG/Metadata/Internal/NpgsqlAnnotationProvider.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,6 @@ public override IEnumerable<IAnnotation> For(IRelationalModel model, bool design
257257
return [];
258258
}
259259

260-
return model.Model.GetAnnotations().Where(
261-
a =>
262-
a.Name.StartsWith(NpgsqlAnnotationNames.PostgresExtensionPrefix, StringComparison.Ordinal)
263-
|| a.Name.StartsWith(NpgsqlAnnotationNames.EnumPrefix, StringComparison.Ordinal)
264-
|| a.Name.StartsWith(NpgsqlAnnotationNames.RangePrefix, StringComparison.Ordinal)
265-
|| a.Name.StartsWith(NpgsqlAnnotationNames.CollationDefinitionPrefix, StringComparison.Ordinal));
260+
return model.Model.GetAnnotations().Where(NpgsqlAnnotationHelper.IsRelationalModelAnnotation);
266261
}
267262
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal;
2+
3+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Internal;
4+
5+
/// <summary>
6+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
7+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
8+
/// any release. You should only use it directly in your code with extreme caution and knowing that
9+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
10+
/// </summary>
11+
public class NpgsqlMigrationsAnnotationProvider : MigrationsAnnotationProvider
12+
{
13+
/// <summary>
14+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
15+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
16+
/// any release. You should only use it directly in your code with extreme caution and knowing that
17+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
18+
/// </summary>
19+
#pragma warning disable EF1001 // Internal EF Core API usage.
20+
public NpgsqlMigrationsAnnotationProvider(MigrationsAnnotationProviderDependencies dependencies)
21+
#pragma warning restore EF1001
22+
: base(dependencies)
23+
{
24+
}
25+
26+
/// <summary>
27+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
28+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
29+
/// any release. You should only use it directly in your code with extreme caution and knowing that
30+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
31+
/// </summary>
32+
public override IEnumerable<IAnnotation> ForRemove(IRelationalModel model)
33+
=> model.Model.GetAnnotations().Where(NpgsqlAnnotationHelper.IsRelationalModelAnnotation);
34+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
2+
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal;
3+
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Internal;
4+
5+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
6+
7+
public class NpgsqlMigrationsAnnotationProviderTest
8+
{
9+
private readonly NpgsqlMigrationsAnnotationProvider _migrationsAnnotationProvider = new(new MigrationsAnnotationProviderDependencies());
10+
11+
[Fact]
12+
public virtual void ForRemove_returns_relational_model_annotations()
13+
{
14+
var modelBuilder = new ModelBuilder()
15+
.HasPostgresExtension("test_extension")
16+
.HasPostgresExtension("test_schema2", "test_extension2")
17+
.HasPostgresEnum("test_enum", ["A", "B", "C"])
18+
.HasPostgresEnum("test_schema2", "test_enum2", ["A", "B", "C"])
19+
.HasPostgresRange("test_range", "test_subtype")
20+
.HasPostgresRange("test_schema2", "test_range2", "test_subtype2")
21+
.HasCollation("test_collation", "test_locale")
22+
.HasCollation("test_schema2", "test_collation2", "test_locale2", "test_provider2", false);
23+
24+
// Define a sequence, so we can verify that the annotation it creates is excluded from the ForRemove() result.
25+
modelBuilder.HasSequence("test_sequence");
26+
27+
var model = new RelationalModel(modelBuilder.FinalizeModel());
28+
var allAnnotations = model.Model.GetAnnotations().ToList();
29+
30+
var annotations = _migrationsAnnotationProvider.ForRemove(model).ToList();
31+
32+
Assert.Equal(9, allAnnotations.Count);
33+
Assert.Equal(8, annotations.Count);
34+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.PostgresExtensionPrefix}test_extension");
35+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.PostgresExtensionPrefix}test_schema2.test_extension2");
36+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.EnumPrefix}test_enum");
37+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.EnumPrefix}test_schema2.test_enum2");
38+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.RangePrefix}test_range");
39+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.RangePrefix}test_schema2.test_range2");
40+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.CollationDefinitionPrefix}test_collation");
41+
Assert.Contains(annotations, a => a.Name == $"{NpgsqlAnnotationNames.CollationDefinitionPrefix}test_schema2.test_collation2");
42+
}
43+
44+
[Fact]
45+
public virtual void ForRemove_handles_no_annotations()
46+
{
47+
var model = new RelationalModel(new Model());
48+
Assert.Empty(_migrationsAnnotationProvider.ForRemove(model));
49+
}
50+
}

0 commit comments

Comments
 (0)