Npgsql 8.0 Release Notes
Npgsql version 8.0 is out and available on nuget.org.
Note
Npgsql 8.0 will be the last version to support .NET Framework (via .NET Standard 2.0). Starting with 9.0, Npgsql will only target .NET TFMs supported at release time (i.e. net6.0
).
NativeAOT and trimming support
Npgsql 8.0 now has 1st-class support for NativeAOT and trimming; the entire library has been properly annotated and is safe for use in applications. The majority of features have been made compatible with NativeAOT/trimming and can be used without issues, and most applications using Npgsql can be used as-is with NativeAOT/trimming without any changes. A few features which are incompatible require an explicit code opt-in, which generates a warning if used with NativeAOT/trimming enabled (see breaking change note).
Considerable effort has gone into reducing Npgsql's size footprint; a minimal Npgsql application using NativeAOT and trimming now takes only around 5MB of disk space. To allow users to achieve a minimal size footprint, NpgsqlSlimDataSourceBuilder has been introduced; unlike the standard NpgsqlDataSourceBuilder, this builder includes only the very minimum of functionality by default, and allows adding additional features via opt-ins. This allows a pay-per-play approach to application size, where developers can choose only the features they actually need for optimal size. For more information, see NpgsqlSlimDataSourceBuilder.
Making Npgsql NativeAOT/trimming-compatible was a far-reaching effort, affecting many parts of the driver and involving a rewrite of large parts of Npgsql's internals (leading to many other internal improvements). This huge task was done mainly by Nino Floris, with considerable contributions by Nikita Kazmin.
OpenTelemetry metrics
Npgsql has emitted metrics for several versions, which provided aggregated metrics on various Npgsql interals; for example, it was possible to follow the state of the connection pool, or to track how many commands are being executed per second. Npgsql 8.0 improves on that by switching from the older EventCounter API to the newer System.Diagnostics.Metrics API, implementing OpenTelemetry metrics. To understand more about the different kinds of metrics APIs in .NET, see these docs.
The new metrics have several advantages over the old one; for one thing, they allow associating multiple dimensions to metrics, e.g. allowing Npgsql to cleanly emit pool-related metrics separately for each data source (or connection string) used in the application. The Npgsql metrics implement the experimental OpenTelemetry semantic conventions for database metrics - adding some additional useful ones - and will evolve as that specification stabilizes.
For more information, see the doc page on metrics.
Register NpgsqlDataSource as a keyed DI service
.NET 8.0 introduced keyed services for dependency injection, allowing multiple services with the same CLR type to be registered in a single DI service provider. This is particularly useful when needing to contact multiple databases from your DI-enabled application:
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddNpgsqlDataSource("Host=localhost;Database=CustomersDB;Username=test;Password=test", serviceKey: DatabaseType.CustomerDb)
.AddNpgsqlDataSource("Host=localhost;Database=OrdersDB;Username=test;Password=test", serviceKey: DatabaseType.OrdersDb);
var app = builder.Build();
app.MapGet("/", async ([FromKeyedServices(DatabaseType.OrdersDb)] NpgsqlConnection connection)
=> connection.ConnectionString);
app.Run();
enum DatabaseType
{
CustomerDb,
OrdersDb
}
In this ASP.NET Minimal API application, two Npgsql data sources are registered in DI - one for a customers database, and another for an orders database. When a data source - or connections - needs to be injected somewhere, an enum is used as the service key, to distinguish which database is being requested (note that connections to both databases can be requested by the same function!).
For more information on registering Npgsql services in DI, see the documentation for Npgsql.DependencyInjection.
Other features
- Allow using nullable value types with the generic
NpgsqlParameter<T>
, e.g.NpgsqlParameter<int?>
. - Introduce a non-caching password provider callback via NpgsqlDataSourceBuilder.UsePasswordProvider.
- Allow customizing System.Text.Json JsonSerializationOptions via NpgsqlDataSourceBuilder.ConfigureJsonOptions.
- Improvements and cleanup for networking type mappings:
- In addition to .NET IPAddress, PostgreSQL
inet
can also mapped to be mapped to NpgsqlInet, which is an immutable struct containing both IP and netmask components. - PostgreSQL
cidr
is now mapped to the newly-introduced NpgsqlCidr. The mapping toValueTuple<IPAddress, int>
has been removed.
- In addition to .NET IPAddress, PostgreSQL
- Allow providing the root certificate programmatically via the new NpgsqlDataSourceBuilder.UseRootCertificate
Version 8.0 contains many other smaller features and bug fixes, see the 8.0.0 milestone for the full list of issues.
Breaking changes
JSON POCO and other dynamic features now require an explicit opt-in
Npgsql 8.0 is fully compatible with NativeAOT and trimming (see above). While most driver capabilities have been made to work in those profiles, certain features involve dynamic coding practices and are incompatible with NativeAOT and/or trimming - at least for now. As a result, these features now require explicit opt-ins (annotated to be incompatible with NativeAOT/trimming), which you must add either on your NpgsqlDataSourceBuilder or on NpgsqlConnection.GlobalTypeMapper:
PostgreSQL type | Default .NET type |
---|---|
JSON POCO mapping, JsonNode and subtypes | EnableDynamicJson |
Unmapped enums, ranges, multiranges | EnableUnmappedTypes |
Read PostgreSQL records as .NET tuples | EnableRecordsAsTuples |
Existing code using the above features will start throwing exceptions after upgrading to Npgsql 8.0; the exceptions provide explicit guidance on how to add the opt-ins.
SSL Mode=Require
no longer validates certificates
tl;dr use SSL Mode=VerifyCA
or VerifyFull
in order to validate certificates provided by PostgreSQL.
In versions of Npgsql older than 6.0, specifying SSL Mode=Require
made Npgsql validate the SSL/TLS certificate provided by PostgreSQL. This did not align with the meaning of "require" in PostgreSQL and other clients, where it simply means that SSL/TLS is required, but without certificate validation. To align with the standard PostgreSQL meaning, starting with Npgsql 6.0 VerifyCA
or VerifyFull
must be specified to validate the certificate.
To prevent existing usage of Require
to silently stop validating, Npgsql 6.0 and 7.0 forced Trust Server Certificate=true
to be specified; this made users aware of the change, guiding them to either switch to VerifyCA
/VerifyFull
(if they want validation) or to add Trust Server Certificate=true
(if they don't). After two major versions, we are now removing the requirement to specify Trust Server Certificate=true
with SSL Mode=Require
; the latter will behave in the standard PostgreSQL way and will not verify certificates.
For more context, see #3988.
IList<T> mapping now requires a generic NpgsqlParameter<T>
Previous versions of Npgsql allowed writing arbitrary list types as PostgreSQL array, as long as they implemented the IList<T>
interface:
await using var command = new NpgsqlCommand("SELECT $1", conn)
{
Parameters = { new NpgsqlParameter { Value = new ReadOnlyCollection<int>(new List<int> { 1, 2, 3 }) } }
};
await using var reader = await command.ExecuteReaderAsync();
This capability has been removed; supporting it required a costly reflection check, which also would be difficult to implement with trimming enabled, potentially increasing binary size in an unacceptable way. As a mitigation, you can instead use the generic NpgsqlParameter<T>
- typed with IList<T>
- to do the same:
await using var command = new NpgsqlCommand("SELECT $1", conn)
{
Parameters = { new NpgsqlParameter<IList<int>> { Value = new ReadOnlyCollection<int>(new List<int> { 1, 2, 3 }) } }
};
await using var reader = await command.ExecuteReaderAsync();
cidr
now maps to NpgsqlCidr
instead of ValueTuple<IPAddress, int>
As part of improving Npgsql's support for the PostgreSQL network mappings (see above), the PostgreSQL cidr
type now maps to the newly-introduced NpgsqlCidr, and can no longer be mapped to ValueTuple<IPAddress, int>
.
Obsoletions and obsolete API removals
NpgsqlTsVector.Parse()
andNpgsqlTsQuery.Parse()
are now obsolete. These methods attempted to mimic the behavior of the PostgreSQLto_tsvector
andto_tsquery
functions, but could only do so partially and in problematic ways. Use the PostgreSQL functions instead.- The parsing functions on the built-in geometry types (NpgsqlPoint, NpgsqlBox etc.) have been removed; similarly, they partially replicated PostgreSQL parsing functionality client-side and had issues.
NpgsqlLargeObjectManager
andNpsgqlLargeObjectStream
are now obsolete. These types were very rarely-used, provided only a thin wrapper over easily-accessible PostgreSQL large-object functions, and limited usage in various ways (e.g. they didn't allow batching). Call the PostgreSQL large-object functions directly.- The
Internal Command Timeout
connection string parameter has been obsoleted. NpgsqlDbType.TimestampTZ
andNpgsqlDbType.TimeTZ
were obsoleted many releases ago, and were finally removed. UseNpgsqlDbType.TimestampTz
andNpgsqlDbType.TimeTz
instead.
Executing a void-returning function returns .NET null instead of DBNull
Previously, executing a void-returning returned DBNull.Value
:
var command = new NpgsqlCommand("SELECT pg_sleep(10)", connection);
var result = await command.ExecuteScalarAsync();
Before 8.0, result
had the value DBNull.Value
; this has been changed in 8.0 to be .NET null
. This is more correct (as there are no results, rather than a result containing NULL), aligns with ADO.NET standard practices and with other drivers.
Plugin APIs have been changed for NativeAOT/trimming support
As part of the effort to make Npgsql compatible with NativeAOT and trimming, the plugin API was changed in fundamental, breaking ways. Although this API never had the stability guarantees of a true public API (it was and still is in an Internal namespace), external plugins which were developed with it will require adjustments.
Contributors
Thank you very much to the following people who have contributed to the individual 8.0.x. releases.
Milestone 8.0.0
Contributor | Assigned issues |
---|---|
@NinoFloris | 43 |
@vonzshik | 23 |
@roji | 20 |
@manandre | 4 |
@BogdanYarotsky | 1 |
@Brar | 1 |
@erikdesj | 1 |
@SoftStoneDevelop | 1 |
@sonquer | 1 |
@yucelkivanc-hepsiburada | 1 |