Skip to content

Commit 30cebf0

Browse files
authored
Add ConfigureDataSource() to NpgsqlDbContextOptionsBuilder (#3277)
Closes #2542 Closes #2704
1 parent f7d7910 commit 30cebf0

File tree

7 files changed

+240
-58
lines changed

7 files changed

+240
-58
lines changed

src/EFCore.PG/Infrastructure/Internal/NpgsqlOptionsExtension.cs

+20
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public virtual Version PostgresVersion
3636
public virtual bool IsPostgresVersionSet
3737
=> _postgresVersion is not null;
3838

39+
/// <summary>
40+
/// A lambda to configure Npgsql options on <see cref="NpgsqlDataSourceBuilder" />.
41+
/// </summary>
42+
public virtual Action<NpgsqlDataSourceBuilder>? DataSourceBuilderAction { get; private set; }
43+
3944
/// <summary>
4045
/// The <see cref="DbDataSource" />, or <see langword="null" /> if a connection string or <see cref="DbConnection" /> was used
4146
/// instead of a <see cref="DbDataSource" />.
@@ -126,6 +131,21 @@ public NpgsqlOptionsExtension(NpgsqlOptionsExtension copyFrom)
126131
public override int? MinBatchSize
127132
=> base.MinBatchSize ?? 2;
128133

134+
/// <summary>
135+
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
136+
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.
137+
/// </summary>
138+
/// <param name="dataSourceBuilderAction">A lambda to configure Npgsql options on <see cref="NpgsqlDataSourceBuilder" />.</param>
139+
/// <returns>A new instance with the option changed.</returns>
140+
public virtual NpgsqlOptionsExtension WithDataSourceConfiguration(Action<NpgsqlDataSourceBuilder> dataSourceBuilderAction)
141+
{
142+
var clone = (NpgsqlOptionsExtension)Clone();
143+
144+
clone.DataSourceBuilderAction = dataSourceBuilderAction;
145+
146+
return clone;
147+
}
148+
129149
/// <summary>
130150
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
131151
/// It is unusual to call this method directly. Instead use <see cref="DbContextOptionsBuilder" />.

src/EFCore.PG/Infrastructure/NpgsqlDbContextOptionsBuilder.cs

+26-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@ public NpgsqlDbContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder)
1818
{
1919
}
2020

21+
/// <summary>
22+
/// Configures lower-level Npgsql options at the ADO.NET driver level.
23+
/// </summary>
24+
/// <param name="dataSourceBuilderAction">A lambda to configure Npgsql options on <see cref="NpgsqlDataSourceBuilder" />.</param>
25+
/// <remarks>
26+
/// Changes made by <see cref="ConfigureDataSource" /> are untracked; When using <see cref="DbContext.OnConfiguring" />, EF Core
27+
/// will by default resolve the same <see cref="NpgsqlDataSource" /> internally, disregarding differing configuration across calls
28+
/// to <see cref="ConfigureDataSource" />. Either make sure that <see cref="ConfigureDataSource" /> always sets the same
29+
/// configuration, or pass externally-provided, pre-configured data source instances when configuring the provider.
30+
/// </remarks>
31+
public virtual NpgsqlDbContextOptionsBuilder ConfigureDataSource(Action<NpgsqlDataSourceBuilder> dataSourceBuilderAction)
32+
=> WithOption(e => e.WithDataSourceConfiguration(dataSourceBuilderAction));
33+
2134
/// <summary>
2235
/// Connect to this database for administrative operations (creating/dropping databases).
2336
/// </summary>
@@ -48,6 +61,8 @@ public virtual NpgsqlDbContextOptionsBuilder SetPostgresVersion(int major, int m
4861
public virtual NpgsqlDbContextOptionsBuilder UseRedshift(bool useRedshift = true)
4962
=> WithOption(e => e.WithRedshift(useRedshift));
5063

64+
#region MapRange
65+
5166
/// <summary>
5267
/// Maps a user-defined PostgreSQL range type for use.
5368
/// </summary>
@@ -95,6 +110,10 @@ public virtual NpgsqlDbContextOptionsBuilder MapRange(
95110
string? subtypeName = null)
96111
=> WithOption(e => e.WithUserRangeDefinition(rangeName, schemaName, subtypeClrType, subtypeName));
97112

113+
#endregion MapRange
114+
115+
#region MapEnum
116+
98117
/// <summary>
99118
/// Maps a PostgreSQL enum type for use.
100119
/// </summary>
@@ -122,6 +141,8 @@ public virtual NpgsqlDbContextOptionsBuilder MapEnum(
122141
INpgsqlNameTranslator? nameTranslator = null)
123142
=> WithOption(e => e.WithEnumMapping(clrType, enumName, schemaName, nameTranslator));
124143

144+
#endregion MapEnum
145+
125146
/// <summary>
126147
/// Appends NULLS FIRST to all ORDER BY clauses. This is important for the tests which were written
127148
/// for SQL Server. Note that to fully implement null-first ordering indexes also need to be generated
@@ -131,32 +152,33 @@ public virtual NpgsqlDbContextOptionsBuilder MapEnum(
131152
internal virtual NpgsqlDbContextOptionsBuilder ReverseNullOrdering(bool reverseNullOrdering = true)
132153
=> WithOption(e => e.WithReverseNullOrdering(reverseNullOrdering));
133154

134-
#region Authentication
155+
#region Authentication (obsolete)
135156

136157
/// <summary>
137158
/// Configures the <see cref="DbContext" /> to use the specified <see cref="ProvideClientCertificatesCallback" />.
138159
/// </summary>
139160
/// <param name="callback">The callback to use.</param>
161+
[Obsolete("Call ConfigureDataSource() and configure the client certificates on the NpgsqlDataSourceBuilder, or pass an externally-built, pre-configured NpgsqlDataSource to UseNpgsql().")]
140162
public virtual NpgsqlDbContextOptionsBuilder ProvideClientCertificatesCallback(ProvideClientCertificatesCallback? callback)
141163
=> WithOption(e => e.WithProvideClientCertificatesCallback(callback));
142164

143165
/// <summary>
144166
/// Configures the <see cref="DbContext" /> to use the specified <see cref="RemoteCertificateValidationCallback" />.
145167
/// </summary>
146168
/// <param name="callback">The callback to use.</param>
169+
[Obsolete("Call ConfigureDataSource() and configure remote certificate validation on the NpgsqlDataSourceBuilder, or pass an externally-built, pre-configured NpgsqlDataSource to UseNpgsql().")]
147170
public virtual NpgsqlDbContextOptionsBuilder RemoteCertificateValidationCallback(RemoteCertificateValidationCallback? callback)
148171
=> WithOption(e => e.WithRemoteCertificateValidationCallback(callback));
149172

150173
/// <summary>
151174
/// Configures the <see cref="DbContext" /> to use the specified <see cref="ProvidePasswordCallback" />.
152175
/// </summary>
153176
/// <param name="callback">The callback to use.</param>
154-
#pragma warning disable CS0618 // ProvidePasswordCallback is obsolete
177+
[Obsolete("Call ConfigureDataSource() and configure the password callback on the NpgsqlDataSourceBuilder, or pass an externally-built, pre-configured NpgsqlDataSource to UseNpgsql().")]
155178
public virtual NpgsqlDbContextOptionsBuilder ProvidePasswordCallback(ProvidePasswordCallback? callback)
156179
=> WithOption(e => e.WithProvidePasswordCallback(callback));
157-
#pragma warning restore CS0618
158180

159-
#endregion Authentication
181+
#endregion Authentication (obsolete)
160182

161183
#region Retrying execution strategy
162184

src/EFCore.PG/Properties/NpgsqlStrings.Designer.cs

+11-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/EFCore.PG/Properties/NpgsqlStrings.resx

+5-2
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@
117117
<resheader name="writer">
118118
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
119119
</resheader>
120-
<data name="TwoDataSourcesInSameServiceProvider" xml:space="preserve">
121-
<value>Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'.</value>
120+
<data name="DataSourceAndConfigNotSupported" xml:space="preserve">
121+
<value>ConfigureDataSource() cannot be used when an externally-provided NpgsqlDataSource is passed to UseNpgsql(). Either perform all data source configuration on the external NpgsqlDataSource, or pass a connection string to UseNpgsql() and specify the data source configuration there.</value>
122122
</data>
123123
<data name="DuplicateColumnCompressionMethodMismatch" xml:space="preserve">
124124
<value>'{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}' but are configured with different compression methods.</value>
@@ -247,4 +247,7 @@
247247
<data name="StoredProcedureReturnValueNotSupported" xml:space="preserve">
248248
<value>The entity type '{entityType}' is mapped to the stored procedure '{sproc}', which is configured with result columns. PostgreSQL stored procedures do not support return values; use output parameters instead.</value>
249249
</data>
250+
<data name="TwoDataSourcesInSameServiceProvider" xml:space="preserve">
251+
<value>Using two distinct data sources within a service provider is not supported, and Entity Framework is not building its own internal service provider. Either allow Entity Framework to build the service provider by removing the call to '{useInternalServiceProvider}', or ensure that the same data source is used for all uses of a given service provider passed to '{useInternalServiceProvider}'.</value>
252+
</data>
250253
</root>

src/EFCore.PG/Storage/Internal/NpgsqlDataSourceManager.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Data.Common;
33
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
44
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
5+
using Npgsql.EntityFrameworkCore.PostgreSQL.Internal;
56

67
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal;
78

@@ -52,7 +53,12 @@ public NpgsqlDataSourceManager(IEnumerable<INpgsqlDataSourceConfigurationPlugin>
5253
// If the user has explicitly passed in a data source via UseNpgsql(), use that.
5354
// Note that in this case, the data source is scoped (not singleton), and so can change between different
5455
// DbContext instances using the same internal service provider.
55-
{ DataSource: DbDataSource dataSource } => dataSource,
56+
{ DataSource: DbDataSource dataSource }
57+
=> npgsqlOptionsExtension.DataSourceBuilderAction is null
58+
? dataSource
59+
// If the user has explicitly passed in a data source via UseNpgsql(), but also supplied a data source configuration
60+
// lambda, throw - we're unable to apply the configuration lambda to the externally-provided, already-built data source.
61+
: throw new NotSupportedException(NpgsqlStrings.DataSourceAndConfigNotSupported),
5662

5763
// If the user has passed in a DbConnection, never use a data source - even if e.g. MapEnum() was called.
5864
// This is to avoid blocking and allow continuing using enums in conjunction with DbConnections (which
@@ -68,6 +74,7 @@ public NpgsqlDataSourceManager(IEnumerable<INpgsqlDataSourceConfigurationPlugin>
6874
{ ConnectionString: null } or null => null,
6975

7076
// The following are features which require an NpgsqlDataSource, since they require configuration on NpgsqlDataSourceBuilder.
77+
{ DataSourceBuilderAction: not null } => GetSingletonDataSource(npgsqlOptionsExtension),
7178
{ EnumDefinitions.Count: > 0 } => GetSingletonDataSource(npgsqlOptionsExtension),
7279
_ when _plugins.Any() => GetSingletonDataSource(npgsqlOptionsExtension),
7380

@@ -139,6 +146,10 @@ enumDefinition.StoreTypeSchema is null
139146
dataSourceBuilder.UseUserCertificateValidationCallback(npgsqlOptionsExtension.RemoteCertificateValidationCallback);
140147
}
141148

149+
// Finally, if the user has provided a data source builder configuration action, invoke it.
150+
// Do this last, to allow the user to override anything set above.
151+
npgsqlOptionsExtension.DataSourceBuilderAction?.Invoke(dataSourceBuilder);
152+
142153
return dataSourceBuilder.Build();
143154
}
144155

test/EFCore.PG.FunctionalTests/LoggingNpgsqlTest.cs

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public void Logs_context_initialization_postgres_version()
1818
ExpectedMessage($"PostgresVersion=10.7 {DefaultOptions}"),
1919
ActualMessage(s => CreateOptionsBuilder(s, b => ((NpgsqlDbContextOptionsBuilder)b).SetPostgresVersion(Version.Parse("10.7")))));
2020

21+
#pragma warning disable CS0618 // Authentication APIs on NpgsqlDbContextOptionsBuilder are obsolete
2122
[Fact]
2223
public void Logs_context_initialization_provide_client_certificates_callback()
2324
=> Assert.Equal(
@@ -42,6 +43,7 @@ public void Logs_context_initialization_remote_certificate_validation_callback()
4243
s => CreateOptionsBuilder(
4344
s,
4445
b => ((NpgsqlDbContextOptionsBuilder)b).RemoteCertificateValidationCallback((_, _, _, _) => true))));
46+
#pragma warning restore CS0618 // Authentication APIs on NpgsqlDbContextOptionsBuilder are obsolete
4547

4648
[Fact]
4749
public void Logs_context_initialization_reverse_null_ordering()

0 commit comments

Comments
 (0)