using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Npgsql.Internal;
using Npgsql.Internal.ResolverFactories;
using Npgsql.NameTranslation;
using Npgsql.Properties;
using Npgsql.TypeMapping;
using NpgsqlTypes;
namespace Npgsql;
///
/// Provides a simple API for configuring and creating an , from which database connections can be obtained.
///
///
/// On this builder, various features are disabled by default; unless you're looking to save on code size (e.g. when publishing with
/// NativeAOT), use instead.
///
public sealed class NpgsqlSlimDataSourceBuilder : INpgsqlTypeMapper
{
static UnsupportedTypeInfoResolver UnsupportedTypeInfoResolver { get; } = new();
ILoggerFactory? _loggerFactory;
bool _sensitiveDataLoggingEnabled;
List>? _tracingOptionsBuilderCallbacks;
List>? _typeLoadingOptionsBuilderCallbacks;
TransportSecurityHandler _transportSecurityHandler = new();
RemoteCertificateValidationCallback? _userCertificateValidationCallback;
Action? _clientCertificatesCallback;
Action? _sslClientAuthenticationOptionsCallback;
Action? _negotiateOptionsCallback;
IntegratedSecurityHandler _integratedSecurityHandler = new();
Func? _passwordProvider;
Func>? _passwordProviderAsync;
Func>? _periodicPasswordProvider;
TimeSpan _periodicPasswordSuccessRefreshInterval, _periodicPasswordFailureRefreshInterval;
List? _dbTypeResolverFactories;
PgTypeInfoResolverChainBuilder _resolverChainBuilder = new(); // mutable struct, don't make readonly.
readonly UserTypeMapper _userTypeMapper;
Action? _connectionInitializer;
Func? _connectionInitializerAsync;
internal JsonSerializerOptions? JsonSerializerOptions { get; private set; }
internal Action ConfigureDefaultFactories { get; set; }
///
/// A connection string builder that can be used to configure the connection string on the builder.
///
public NpgsqlConnectionStringBuilder ConnectionStringBuilder { get; }
///
/// Returns the connection string, as currently configured on the builder.
///
public string ConnectionString => ConnectionStringBuilder.ToString();
static NpgsqlSlimDataSourceBuilder()
=> GlobalTypeMapper.Instance.AddGlobalTypeMappingResolvers([new AdoTypeInfoResolverFactory()]);
///
/// A diagnostics name used by Npgsql when generating tracing, logging and metrics.
///
public string? Name { get; set; }
///
/// Constructs a new , optionally starting out from the given
/// .
///
public NpgsqlSlimDataSourceBuilder(string? connectionString = null)
: this(new NpgsqlConnectionStringBuilder(connectionString))
{}
internal NpgsqlSlimDataSourceBuilder(NpgsqlConnectionStringBuilder connectionStringBuilder)
{
ConnectionStringBuilder = connectionStringBuilder;
_userTypeMapper = new() { DefaultNameTranslator = GlobalTypeMapper.Instance.DefaultNameTranslator };
ConfigureDefaultFactories = static instance => instance.AppendDefaultFactories();
ConfigureResolverChain = static chain => chain.Add(UnsupportedTypeInfoResolver);
}
///
/// Sets the that will be used for logging.
///
/// The logger factory to be used.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UseLoggerFactory(ILoggerFactory? loggerFactory)
{
_loggerFactory = loggerFactory;
return this;
}
///
/// Enables parameters to be included in logging. This includes potentially sensitive information from data sent to PostgreSQL.
/// You should only enable this flag in development, or if you have the appropriate security measures in place based on the
/// sensitivity of this data.
///
/// If , then sensitive data is logged.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableParameterLogging(bool parameterLoggingEnabled = true)
{
_sensitiveDataLoggingEnabled = parameterLoggingEnabled;
return this;
}
///
/// Configure type loading options for the DataSource. Calling this again will replace
/// the prior action.
///
public NpgsqlSlimDataSourceBuilder ConfigureTypeLoading(Action configureAction)
{
ArgumentNullException.ThrowIfNull(configureAction);
_typeLoadingOptionsBuilderCallbacks ??= new();
_typeLoadingOptionsBuilderCallbacks.Add(configureAction);
return this;
}
///
/// Configures OpenTelemetry tracing options.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder ConfigureTracing(Action configureAction)
{
ArgumentNullException.ThrowIfNull(configureAction);
_tracingOptionsBuilderCallbacks ??= new();
_tracingOptionsBuilderCallbacks.Add(configureAction);
return this;
}
///
/// Configures the JSON serializer options used when reading and writing all System.Text.Json data.
///
/// Options to customize JSON serialization and deserialization.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder ConfigureJsonOptions(JsonSerializerOptions serializerOptions)
{
ArgumentNullException.ThrowIfNull(serializerOptions);
JsonSerializerOptions = serializerOptions;
return this;
}
#region Authentication
///
/// When using SSL/TLS, this is a callback that allows customizing how the PostgreSQL-provided certificate is verified. This is an
/// advanced API, consider using or instead.
///
/// The callback containing custom callback verification logic.
///
///
/// Cannot be used in conjunction with , or
/// .
///
///
/// See .
///
///
/// The same builder instance so that multiple calls can be chained.
[Obsolete("Use UseSslClientAuthenticationOptionsCallback")]
public NpgsqlSlimDataSourceBuilder UseUserCertificateValidationCallback(
RemoteCertificateValidationCallback userCertificateValidationCallback)
{
_userCertificateValidationCallback = userCertificateValidationCallback;
return this;
}
///
/// Specifies an SSL/TLS certificate which Npgsql will send to PostgreSQL for certificate-based authentication.
///
/// The client certificate to be sent to PostgreSQL when opening a connection.
/// The same builder instance so that multiple calls can be chained.
[Obsolete("Use UseSslClientAuthenticationOptionsCallback")]
public NpgsqlSlimDataSourceBuilder UseClientCertificate(X509Certificate? clientCertificate)
{
if (clientCertificate is null)
return UseClientCertificatesCallback(null);
var clientCertificates = new X509CertificateCollection { clientCertificate };
return UseClientCertificates(clientCertificates);
}
///
/// Specifies a collection of SSL/TLS certificates which Npgsql will send to PostgreSQL for certificate-based authentication.
///
/// The client certificate collection to be sent to PostgreSQL when opening a connection.
/// The same builder instance so that multiple calls can be chained.
[Obsolete("Use UseSslClientAuthenticationOptionsCallback")]
public NpgsqlSlimDataSourceBuilder UseClientCertificates(X509CertificateCollection? clientCertificates)
=> UseClientCertificatesCallback(clientCertificates is null ? null : certs => certs.AddRange(clientCertificates));
///
/// When using SSL/TLS, this is a callback that allows customizing SslStream's authentication options.
///
/// The callback to customize SslStream's authentication options.
///
///
/// See .
///
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UseSslClientAuthenticationOptionsCallback(Action? sslClientAuthenticationOptionsCallback)
{
_sslClientAuthenticationOptionsCallback = sslClientAuthenticationOptionsCallback;
return this;
}
///
/// Specifies a callback to modify the collection of SSL/TLS client certificates which Npgsql will send to PostgreSQL for
/// certificate-based authentication. This is an advanced API, consider using or
/// instead.
///
/// The callback to modify the client certificate collection.
///
///
/// The callback is invoked every time a physical connection is opened, and is therefore suitable for rotating short-lived client
/// certificates. Simply make sure the certificate collection argument has the up-to-date certificate(s).
///
///
/// The callback's collection argument already includes any client certificates specified via the connection string or environment
/// variables.
///
///
/// The same builder instance so that multiple calls can be chained.
[Obsolete("Use UseSslClientAuthenticationOptionsCallback")]
public NpgsqlSlimDataSourceBuilder UseClientCertificatesCallback(Action? clientCertificatesCallback)
{
_clientCertificatesCallback = clientCertificatesCallback;
return this;
}
///
/// Sets the that will be used validate SSL certificate, received from the server.
///
/// The CA certificate.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UseRootCertificate(X509Certificate2? rootCertificate)
=> rootCertificate is null
? UseRootCertificatesCallback((Func?)null)
: UseRootCertificateCallback(() => rootCertificate);
///
/// Sets the that will be used validate SSL certificate, received from the server.
///
/// The CA certificates.
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UseRootCertificates(X509Certificate2Collection? rootCertificates)
=> rootCertificates is null
? UseRootCertificatesCallback((Func?)null)
: UseRootCertificatesCallback(() => rootCertificates);
///
/// Specifies a callback that will be used to validate SSL certificate, received from the server.
///
/// The callback to get CA certificate.
/// The same builder instance so that multiple calls can be chained.
///
/// This overload, which accepts a callback, is suitable for scenarios where the certificate rotates
/// and might change during the lifetime of the application.
/// When that's not the case, use the overload which directly accepts the certificate.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UseRootCertificateCallback(Func? rootCertificateCallback)
{
_transportSecurityHandler.RootCertificatesCallback = () => rootCertificateCallback is not null
? new X509Certificate2Collection(rootCertificateCallback())
: null;
return this;
}
///
/// Specifies a callback that will be used to validate SSL certificate, received from the server.
///
/// The callback to get CA certificates.
/// The same builder instance so that multiple calls can be chained.
///
/// This overload, which accepts a callback, is suitable for scenarios where the certificate rotates
/// and might change during the lifetime of the application.
/// When that's not the case, use the overload which directly accepts the certificate.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UseRootCertificatesCallback(Func? rootCertificateCallback)
{
_transportSecurityHandler.RootCertificatesCallback = rootCertificateCallback;
return this;
}
///
/// Configures a periodic password provider, which is automatically called by the data source at some regular interval. This is the
/// recommended way to fetch a rotating access token.
///
/// A callback which returns the password to be sent to PostgreSQL.
/// How long to cache the password before re-invoking the callback.
///
/// If a password refresh attempt fails, it will be re-attempted with this interval.
/// This should typically be much lower than .
///
/// The same builder instance so that multiple calls can be chained.
///
///
/// The provided callback is invoked in a timer, and not when opening connections. It therefore doesn't affect opening time.
///
///
/// The provided cancellation token is only triggered when the entire data source is disposed. If you'd like to apply a timeout to the
/// token fetching, do so within the provided callback.
///
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UsePeriodicPasswordProvider(
Func>? passwordProvider,
TimeSpan successRefreshInterval,
TimeSpan failureRefreshInterval)
{
if (successRefreshInterval < TimeSpan.Zero)
throw new ArgumentException(
string.Format(NpgsqlStrings.ArgumentMustBePositive, nameof(successRefreshInterval)), nameof(successRefreshInterval));
if (failureRefreshInterval < TimeSpan.Zero)
throw new ArgumentException(
string.Format(NpgsqlStrings.ArgumentMustBePositive, nameof(failureRefreshInterval)), nameof(failureRefreshInterval));
_periodicPasswordProvider = passwordProvider;
_periodicPasswordSuccessRefreshInterval = successRefreshInterval;
_periodicPasswordFailureRefreshInterval = failureRefreshInterval;
return this;
}
///
/// Configures a password provider, which is called by the data source when opening connections.
///
///
/// A callback that may be invoked during which returns the password to be sent to PostgreSQL.
///
///
/// A callback that may be invoked during which returns the password to be sent to PostgreSQL.
///
/// The same builder instance so that multiple calls can be chained.
///
///
/// The provided callback is invoked when opening connections. Therefore its important the callback internally depends on cached
/// data or returns quickly otherwise. Any unnecessary delay will affect connection opening time.
///
///
public NpgsqlSlimDataSourceBuilder UsePasswordProvider(
Func? passwordProvider,
Func>? passwordProviderAsync)
{
if (passwordProvider is null != passwordProviderAsync is null)
throw new ArgumentException(NpgsqlStrings.SyncAndAsyncPasswordProvidersRequired);
_passwordProvider = passwordProvider;
_passwordProviderAsync = passwordProviderAsync;
return this;
}
///
/// When using Kerberos, this is a callback that allows customizing default settings for Kerberos authentication.
///
/// The callback containing logic to customize Kerberos authentication settings.
///
///
/// See .
///
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UseNegotiateOptionsCallback(Action? negotiateOptionsCallback)
{
_negotiateOptionsCallback = negotiateOptionsCallback;
return this;
}
#endregion Authentication
#region Type mapping
///
public INpgsqlNameTranslator DefaultNameTranslator
{
get => _userTypeMapper.DefaultNameTranslator;
set => _userTypeMapper.DefaultNameTranslator = value;
}
///
/// Maps a CLR enum to a PostgreSQL enum type.
///
///
/// CLR enum labels are mapped by name to PostgreSQL enum labels.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// You can also use the on your enum fields to manually specify a PostgreSQL enum label.
/// If there is a discrepancy between the .NET and database labels while an enum is read or written,
/// an exception will be raised.
///
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to .
///
/// The .NET enum type to be mapped
public NpgsqlSlimDataSourceBuilder MapEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
where TEnum : struct, Enum
{
_userTypeMapper.MapEnum(pgName, nameTranslator);
return this;
}
///
public bool UnmapEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
where TEnum : struct, Enum
=> _userTypeMapper.UnmapEnum(pgName, nameTranslator);
///
/// Maps a CLR enum to a PostgreSQL enum type.
///
///
/// CLR enum labels are mapped by name to PostgreSQL enum labels.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// You can also use the on your enum fields to manually specify a PostgreSQL enum label.
/// If there is a discrepancy between the .NET and database labels while an enum is read or written,
/// an exception will be raised.
///
/// The .NET enum type to be mapped
///
/// A PostgreSQL type name for the corresponding enum type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to .
///
[RequiresDynamicCode("Calling MapEnum with a Type can require creating new generic types or methods. This may not work when AOT compiling.")]
public NpgsqlSlimDataSourceBuilder MapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
{
_userTypeMapper.MapEnum(clrType, pgName, nameTranslator);
return this;
}
///
public bool UnmapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
=> _userTypeMapper.UnmapEnum(clrType, pgName, nameTranslator);
///
/// Maps a CLR type to a PostgreSQL composite type.
///
///
/// CLR fields and properties by string to PostgreSQL names.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// You can also use the on your members to manually specify a PostgreSQL name.
/// If there is a discrepancy between the .NET type and database type while a composite is read or written,
/// an exception will be raised.
///
///
/// A PostgreSQL type name for the corresponding composite type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to .
///
/// The .NET type to be mapped
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public NpgsqlSlimDataSourceBuilder MapComposite<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] T>(
string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
{
_userTypeMapper.MapComposite(typeof(T), pgName, nameTranslator);
return this;
}
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public bool UnmapComposite<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] T>(
string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
=> _userTypeMapper.UnmapComposite(typeof(T), pgName, nameTranslator);
///
/// Maps a CLR type to a composite type.
///
///
/// Maps CLR fields and properties by string to PostgreSQL names.
/// The translation strategy can be controlled by the parameter,
/// which defaults to .
/// If there is a discrepancy between the .NET type and database type while a composite is read or written,
/// an exception will be raised.
///
/// The .NET type to be mapped.
///
/// A PostgreSQL type name for the corresponding composite type in the database.
/// If null, the name translator given in will be used.
///
///
/// A component which will be used to translate CLR names (e.g. SomeClass) into database names (e.g. some_class).
/// Defaults to .
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public NpgsqlSlimDataSourceBuilder MapComposite([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
{
_userTypeMapper.MapComposite(clrType, pgName, nameTranslator);
return this;
}
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
public bool UnmapComposite([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)]
Type clrType, string? pgName = null, INpgsqlNameTranslator? nameTranslator = null)
=> _userTypeMapper.UnmapComposite(clrType, pgName, nameTranslator);
///
void INpgsqlTypeMapper.AddDbTypeResolverFactory(DbTypeResolverFactory factory)
=> (_dbTypeResolverFactories ??= new()).Add(factory);
///
[Experimental(NpgsqlDiagnostics.ConvertersExperimental)]
public void AddTypeInfoResolverFactory(PgTypeInfoResolverFactory factory)
=> _resolverChainBuilder.PrependResolverFactory(factory);
///
void INpgsqlTypeMapper.Reset() => _resolverChainBuilder.Clear();
internal Action> ConfigureResolverChain { get; set; }
internal void AppendResolverFactory(PgTypeInfoResolverFactory factory)
=> _resolverChainBuilder.AppendResolverFactory(factory);
internal void AppendResolverFactory(Func factory) where T : PgTypeInfoResolverFactory
=> _resolverChainBuilder.AppendResolverFactory(factory);
internal void AppendDefaultFactories()
{
// When used publicly we start off with our slim defaults.
_resolverChainBuilder.AppendResolverFactory(_userTypeMapper);
if (GlobalTypeMapper.Instance.GetUserMappingsResolverFactory() is { } userMappingsResolverFactory)
_resolverChainBuilder.AppendResolverFactory(userMappingsResolverFactory);
foreach (var factory in GlobalTypeMapper.Instance.GetPluginResolverFactories())
_resolverChainBuilder.AppendResolverFactory(factory);
_resolverChainBuilder.AppendResolverFactory(new AdoTypeInfoResolverFactory());
}
#endregion Type mapping
#region Optional opt-ins
///
/// Sets up mappings for the PostgreSQL array types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableArrays()
{
_resolverChainBuilder.EnableArrays();
return this;
}
///
/// Sets up mappings for the PostgreSQL range types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableRanges()
{
_resolverChainBuilder.EnableRanges();
return this;
}
///
/// Sets up mappings for the PostgreSQL multirange types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableMultiranges()
{
_resolverChainBuilder.EnableMultiranges();
return this;
}
///
/// Sets up mappings for the PostgreSQL record type as a .NET object[].
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableRecords()
{
AddTypeInfoResolverFactory(new RecordTypeInfoResolverFactory());
return this;
}
///
/// Sets up mappings for the PostgreSQL tsquery and tsvector types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableFullTextSearch()
{
AddTypeInfoResolverFactory(new FullTextSearchTypeInfoResolverFactory());
return this;
}
///
/// Sets up mappings for the PostgreSQL ltree extension types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableLTree()
{
AddTypeInfoResolverFactory(new LTreeTypeInfoResolverFactory());
return this;
}
///
/// Sets up mappings for the PostgreSQL cube extension type.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableCube()
{
AddTypeInfoResolverFactory(new CubeTypeInfoResolverFactory());
return this;
}
///
/// Sets up mappings for extra conversions from PostgreSQL to .NET types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableExtraConversions()
{
AddTypeInfoResolverFactory(new ExtraConversionResolverFactory());
return this;
}
///
/// Enables the possibility to use TLS/SSl encryption for connections to PostgreSQL. This does not guarantee that encryption will
/// actually be used; see for more details.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableTransportSecurity()
{
_transportSecurityHandler = new RealTransportSecurityHandler();
return this;
}
///
/// Enables the possibility to use GSS/SSPI authentication and encryption for connections to PostgreSQL. This does not guarantee that it will
/// actually be used; see for more details.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableIntegratedSecurity()
{
_integratedSecurityHandler = new RealIntegratedSecurityHandler();
return this;
}
///
/// Sets up network mappings. This allows mapping PhysicalAddress, IPAddress, NpgsqlInet and NpgsqlCidr types
/// to PostgreSQL macaddr, macaddr8, inet and cidr types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableNetworkTypes()
{
_resolverChainBuilder.AppendResolverFactory(new NetworkTypeInfoResolverFactory());
return this;
}
///
/// Sets up network mappings. This allows mapping types like NpgsqlPoint and NpgsqlPath
/// to PostgreSQL point, path and so on types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableGeometricTypes()
{
_resolverChainBuilder.AppendResolverFactory(new GeometricTypeInfoResolverFactory());
return this;
}
///
/// Sets up System.Text.Json mappings. This allows mapping JsonDocument and JsonElement types to PostgreSQL json and jsonb
/// types.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder EnableJsonTypes()
{
_resolverChainBuilder.AppendResolverFactory(() => new JsonTypeInfoResolverFactory(JsonSerializerOptions));
return this;
}
///
/// Sets up dynamic System.Text.Json mappings. This allows mapping arbitrary .NET types to PostgreSQL json and jsonb
/// types, as well as and its derived types.
///
///
/// A list of CLR types to map to PostgreSQL jsonb (no need to specify ).
///
///
/// A list of CLR types to map to PostgreSQL json (no need to specify ).
///
///
/// Due to the dynamic nature of these mappings, they are not compatible with NativeAOT or trimming.
///
/// The same builder instance so that multiple calls can be chained.
[RequiresUnreferencedCode("Json serializer may perform reflection on trimmed types.")]
[RequiresDynamicCode("Serializing arbitrary types to json can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
public NpgsqlSlimDataSourceBuilder EnableDynamicJson(
Type[]? jsonbClrTypes = null,
Type[]? jsonClrTypes = null)
{
_resolverChainBuilder.AppendResolverFactory(() => new JsonDynamicTypeInfoResolverFactory(jsonbClrTypes, jsonClrTypes, JsonSerializerOptions));
return this;
}
///
/// Sets up mappings for the PostgreSQL record type as a .NET or .
///
/// The same builder instance so that multiple calls can be chained.
[RequiresUnreferencedCode("The mapping of PostgreSQL records as .NET tuples requires reflection usage which is incompatible with trimming.")]
[RequiresDynamicCode("The mapping of PostgreSQL records as .NET tuples requires dynamic code usage which is incompatible with NativeAOT.")]
public NpgsqlSlimDataSourceBuilder EnableRecordsAsTuples()
{
AddTypeInfoResolverFactory(new TupledRecordTypeInfoResolverFactory());
return this;
}
///
/// Sets up mappings allowing the use of unmapped enum, range and multirange types.
///
/// The same builder instance so that multiple calls can be chained.
[RequiresUnreferencedCode("The use of unmapped enums, ranges or multiranges requires reflection usage which is incompatible with trimming.")]
[RequiresDynamicCode("The use of unmapped enums, ranges or multiranges requires dynamic code usage which is incompatible with NativeAOT.")]
public NpgsqlSlimDataSourceBuilder EnableUnmappedTypes()
{
AddTypeInfoResolverFactory(new UnmappedTypeInfoResolverFactory());
return this;
}
#endregion Optional opt-ins
///
/// Register a connection initializer, which allows executing arbitrary commands when a physical database connection is first opened.
///
///
/// A synchronous connection initialization lambda, which will be called from when a new physical
/// connection is opened.
///
///
/// An asynchronous connection initialization lambda, which will be called from
/// when a new physical connection is opened.
///
///
/// If an initializer is registered, both sync and async versions must be provided. If you do not use sync APIs in your code, simply
/// throw , which would also catch accidental cases of sync opening.
///
///
/// Take care that the setting you apply in the initializer does not get reverted when the connection is returned to the pool, since
/// Npgsql sends DISCARD ALL by default. The option can be used to
/// turn this off.
///
/// The same builder instance so that multiple calls can be chained.
public NpgsqlSlimDataSourceBuilder UsePhysicalConnectionInitializer(
Action? connectionInitializer,
Func? connectionInitializerAsync)
{
if (connectionInitializer is null != connectionInitializerAsync is null)
throw new ArgumentException(NpgsqlStrings.SyncAndAsyncConnectionInitializersRequired);
_connectionInitializer = connectionInitializer;
_connectionInitializerAsync = connectionInitializerAsync;
return this;
}
///
/// Builds and returns an which is ready for use.
///
public NpgsqlDataSource Build()
{
ConnectionStringBuilder.PostProcessAndValidate();
var (connectionStringBuilder, config) = PrepareConfiguration();
if (ConnectionStringBuilder.Host!.Contains(','))
{
ValidateMultiHost();
return new NpgsqlMultiHostDataSource(connectionStringBuilder, config);
}
return ConnectionStringBuilder.Pooling
? new PoolingDataSource(connectionStringBuilder, config)
: new UnpooledDataSource(connectionStringBuilder, config);
}
///
/// Builds and returns a which is ready for use for load-balancing and failover scenarios.
///
public NpgsqlMultiHostDataSource BuildMultiHost()
{
ConnectionStringBuilder.PostProcessAndValidate();
var (connectionStringBuilder, config) = PrepareConfiguration();
ValidateMultiHost();
return new(connectionStringBuilder, config);
}
// Used in testing.
internal (NpgsqlConnectionStringBuilder, NpgsqlDataSourceConfiguration) PrepareConfiguration()
{
var connectionStringBuilder = ConnectionStringBuilder.Clone();
var sslClientAuthenticationOptionsCallback = _sslClientAuthenticationOptionsCallback;
var hasCertificateCallbacks = _userCertificateValidationCallback is not null || _clientCertificatesCallback is not null;
if (sslClientAuthenticationOptionsCallback is not null && hasCertificateCallbacks)
{
throw new NotSupportedException(NpgsqlStrings.SslClientAuthenticationOptionsCallbackWithOtherCallbacksNotSupported);
}
if (sslClientAuthenticationOptionsCallback is null && hasCertificateCallbacks)
{
sslClientAuthenticationOptionsCallback = options =>
{
if (_clientCertificatesCallback is not null)
{
options.ClientCertificates ??= new X509Certificate2Collection();
_clientCertificatesCallback.Invoke(options.ClientCertificates);
}
if (_userCertificateValidationCallback is not null)
{
options.RemoteCertificateValidationCallback = _userCertificateValidationCallback;
}
};
}
if (!_transportSecurityHandler.SupportEncryption && sslClientAuthenticationOptionsCallback is not null)
{
throw new InvalidOperationException(NpgsqlStrings.TransportSecurityDisabled);
}
if (_passwordProvider is not null && _periodicPasswordProvider is not null)
{
throw new NotSupportedException(NpgsqlStrings.CannotSetMultiplePasswordProviderKinds);
}
if ((_passwordProvider is not null || _periodicPasswordProvider is not null) &&
(connectionStringBuilder.Password is not null || connectionStringBuilder.Passfile is not null))
{
throw new NotSupportedException(NpgsqlStrings.CannotSetBothPasswordProviderAndPassword);
}
ConfigureDefaultFactories(this);
var typeLoadingOptionsBuilder = new NpgsqlTypeLoadingOptionsBuilder();
#pragma warning disable CS0618 // Type or member is obsolete
typeLoadingOptionsBuilder.EnableTableCompositesLoading(connectionStringBuilder.LoadTableComposites);
typeLoadingOptionsBuilder.EnableTypeLoading(connectionStringBuilder.ServerCompatibilityMode is not ServerCompatibilityMode.NoTypeLoading);
#pragma warning restore CS0618 // Type or member is obsolete
foreach (var callback in _typeLoadingOptionsBuilderCallbacks ?? (IEnumerable>)[])
callback.Invoke(typeLoadingOptionsBuilder);
var typeLoadingOptions = typeLoadingOptionsBuilder.Build();
var tracingOptionsBuilder = new NpgsqlTracingOptionsBuilder();
foreach (var callback in _tracingOptionsBuilderCallbacks ?? (IEnumerable>)[])
callback.Invoke(tracingOptionsBuilder);
var tracingOptions = tracingOptionsBuilder.Build();
return (connectionStringBuilder, new(
Name,
_loggerFactory is null
? NpgsqlLoggingConfiguration.NullConfiguration
: new NpgsqlLoggingConfiguration(_loggerFactory, _sensitiveDataLoggingEnabled),
tracingOptions,
typeLoadingOptions,
_transportSecurityHandler,
_integratedSecurityHandler,
sslClientAuthenticationOptionsCallback,
_passwordProvider,
_passwordProviderAsync,
_periodicPasswordProvider,
_periodicPasswordSuccessRefreshInterval,
_periodicPasswordFailureRefreshInterval,
_resolverChainBuilder.Build(ConfigureResolverChain),
_dbTypeResolverFactories ?? [],
DefaultNameTranslator,
_connectionInitializer,
_connectionInitializerAsync,
_negotiateOptionsCallback));
}
void ValidateMultiHost()
{
if (ConnectionStringBuilder.ReplicationMode != ReplicationMode.Off)
throw new NotSupportedException("Replication is not supported with multiple hosts");
}
INpgsqlTypeMapper INpgsqlTypeMapper.ConfigureJsonOptions(JsonSerializerOptions serializerOptions)
=> ConfigureJsonOptions(serializerOptions);
[RequiresUnreferencedCode("Json serializer may perform reflection on trimmed types.")]
[RequiresDynamicCode(
"Serializing arbitrary types to json can require creating new generic types or methods, which requires creating code at runtime. This may not work when AOT compiling.")]
INpgsqlTypeMapper INpgsqlTypeMapper.EnableDynamicJson(Type[]? jsonbClrTypes, Type[]? jsonClrTypes)
=> EnableDynamicJson(jsonbClrTypes, jsonClrTypes);
[RequiresUnreferencedCode(
"The mapping of PostgreSQL records as .NET tuples requires reflection usage which is incompatible with trimming.")]
[RequiresDynamicCode(
"The mapping of PostgreSQL records as .NET tuples requires dynamic code usage which is incompatible with NativeAOT.")]
INpgsqlTypeMapper INpgsqlTypeMapper.EnableRecordsAsTuples()
=> EnableRecordsAsTuples();
[RequiresUnreferencedCode(
"The use of unmapped enums, ranges or multiranges requires reflection usage which is incompatible with trimming.")]
[RequiresDynamicCode(
"The use of unmapped enums, ranges or multiranges requires dynamic code usage which is incompatible with NativeAOT.")]
INpgsqlTypeMapper INpgsqlTypeMapper.EnableUnmappedTypes()
=> EnableUnmappedTypes();
///
INpgsqlTypeMapper INpgsqlTypeMapper.MapEnum<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(string? pgName, INpgsqlNameTranslator? nameTranslator)
{
_userTypeMapper.MapEnum(pgName, nameTranslator);
return this;
}
///
[RequiresDynamicCode("Calling MapEnum with a Type can require creating new generic types or methods. This may not work when AOT compiling.")]
INpgsqlTypeMapper INpgsqlTypeMapper.MapEnum([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
Type clrType, string? pgName, INpgsqlNameTranslator? nameTranslator)
{
_userTypeMapper.MapEnum(clrType, pgName, nameTranslator);
return this;
}
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
INpgsqlTypeMapper INpgsqlTypeMapper.MapComposite<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] T>(
string? pgName, INpgsqlNameTranslator? nameTranslator)
{
_userTypeMapper.MapComposite(typeof(T), pgName, nameTranslator);
return this;
}
///
[RequiresDynamicCode("Mapping composite types involves serializing arbitrary types which can require creating new generic types or methods. This is currently unsupported with NativeAOT, vote on issue #5303 if this is important to you.")]
INpgsqlTypeMapper INpgsqlTypeMapper.MapComposite([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)]
Type clrType, string? pgName, INpgsqlNameTranslator? nameTranslator)
{
_userTypeMapper.MapComposite(clrType, pgName, nameTranslator);
return this;
}
}