diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index 3788670e0..87bce1171 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -17,6 +17,9 @@ public class NpgsqlRelationalConnection : RelationalConnection, INpgsqlRelationa private readonly ProvideClientCertificatesCallback? _provideClientCertificatesCallback; private readonly RemoteCertificateValidationCallback? _remoteCertificateValidationCallback; + // True when a connection was supplied after construction, e.g. via Database.SetDbConnection(). It isn't reflected in the options. + private bool _isDbConnectionSet; + #pragma warning disable CS0618 // ProvidePasswordCallback is obsolete private readonly ProvidePasswordCallback? _providePasswordCallback; #pragma warning restore CS0618 @@ -147,6 +150,19 @@ public override string? ConnectionString } } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// 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 override void SetDbConnection(DbConnection? value, bool contextOwnsConnection) + { + base.SetDbConnection(value, contextOwnsConnection); + + _isDbConnectionSet = value is not null; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -205,7 +221,7 @@ public virtual INpgsqlRelationalConnection CreateAdminConnection() // Cloned connections must be owned so disposing the wrapper also closes and disposes them. var adminNpgsqlOptions = DataSource is not null ? npgsqlOptions.WithConnection(((NpgsqlConnection)CreateDbConnection()).CloneWith(adminConnectionString), owned: true) - : npgsqlOptions.Connection is not null + : npgsqlOptions.Connection is not null || _isDbConnectionSet ? npgsqlOptions.WithConnection(DbConnection.CloneWith(adminConnectionString), owned: true) : npgsqlOptions.WithConnectionString(adminConnectionString); diff --git a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs index c16a173f0..6a96209e7 100644 --- a/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs +++ b/test/EFCore.PG.FunctionalTests/ConnectionSpecificationTest.cs @@ -278,6 +278,33 @@ public async Task Can_create_admin_connection_with_connection() adminConnection.Open(); } + [Fact] + public async Task Can_create_admin_connection_with_connection_set_via_SetDbConnection() + { + await using var _ = await NpgsqlTestStore.GetNorthwindStoreAsync(); + + // Once opened, Npgsql removes the password from the connection's ConnectionString (Persist Security Info=false), so the admin + // connection must be cloned from the connection rather than rebuilt from its connection string. + await using var connection = new NpgsqlConnection(NpgsqlTestStore.NorthwindConnectionString); + await connection.OpenAsync(); + + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseNpgsql(b => b.ApplyConfiguration()); + await using var context = new GeneralOptionsContext(optionsBuilder.Options); + context.Database.SetDbConnection(connection); + + var relationalConnection = context.GetService(); + await using (var adminConnection = relationalConnection.CreateAdminConnection()) + { + Assert.Equal("postgres", new NpgsqlConnectionStringBuilder(adminConnection.ConnectionString).Database); + + await adminConnection.OpenAsync(CancellationToken.None); + } + + // Disposing the admin connection must not dispose the connection supplied by the user + Assert.Equal(System.Data.ConnectionState.Open, connection.State); + } + private class GeneralOptionsContext(DbContextOptions options) : NorthwindContextBase(options); #endregion