diff --git a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs index c2834eed5..5dc0869dd 100644 --- a/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs +++ b/src/EFCore.PG/Migrations/Internal/NpgsqlHistoryRepository.cs @@ -1,4 +1,5 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Conventions; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata.Internal; namespace Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Internal; @@ -105,7 +106,16 @@ protected override IReadOnlyList GetCreateCommands() var model = EnsureModel(); var operations = Dependencies.ModelDiffer.GetDifferences(null, model.GetRelationalModel()); - var commandList = Dependencies.MigrationsSqlGenerator.Generate(operations, model); + + // Workaround for https://github.com/npgsql/efcore.pg/issues/3496: filter out extension-related operations. + // On managed PostgreSQL services (e.g. Azure Flexible Server), CREATE EXTENSION requires sometimes superuser privileges + // even with IF NOT EXISTS. Since extension creation isn't needed for the history table, we exclude these + // operations to avoid permission errors for non-superuser application accounts. + var operationsWithoutExtensions = operations + .Where(o => !o.GetAnnotations().Any(a => a.Name.StartsWith(NpgsqlAnnotationNames.PostgresExtensionPrefix, StringComparison.Ordinal))) + .ToList(); + + var commandList = Dependencies.MigrationsSqlGenerator.Generate(operationsWithoutExtensions, model); return commandList; } diff --git a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs index 046271648..2df3a8aa2 100644 --- a/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs +++ b/test/EFCore.PG.Tests/Migrations/NpgsqlHistoryRepositoryTest.cs @@ -77,6 +77,28 @@ IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'my') THEN CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") ); +""", sql, ignoreLineEndingDifferences: true); + } + + [ConditionalFact] + public void GetCreateIfNotExistsScript_works_with_schema_and_extension() + { + var sql = CreateHistoryRepositoryWithNetTopologySuite("my").GetCreateIfNotExistsScript(); + + Assert.Equal( + """ +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_namespace WHERE nspname = 'my') THEN + CREATE SCHEMA my; + END IF; +END $EF$; +CREATE TABLE IF NOT EXISTS my."__EFMigrationsHistory" ( + "MigrationId" character varying(150) NOT NULL, + "ProductVersion" character varying(32) NOT NULL, + CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") +); + """, sql, ignoreLineEndingDifferences: true); } @@ -156,6 +178,19 @@ private static IHistoryRepository CreateHistoryRepository(string schema = null) .Options) .GetService(); + private static IHistoryRepository CreateHistoryRepositoryWithNetTopologySuite(string schema = null) + => new TestDbContext( + new DbContextOptionsBuilder() + .UseInternalServiceProvider( + NpgsqlTestHelpers.Instance.CreateServiceProvider( + new ServiceCollection().AddEntityFrameworkNpgsqlNetTopologySuite())) + .UseNpgsql( + new NpgsqlConnection("Host=localhost;Database=DummyDatabase"), + b => b.MigrationsHistoryTable(HistoryRepository.DefaultTableName, schema) + .UseNetTopologySuite()) + .Options) + .GetService(); + private class TestDbContext(DbContextOptions options) : DbContext(options) { public DbSet Blogs { get; set; }