Table of Contents

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

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() and NpgsqlTsQuery.Parse() are now obsolete. These methods attempted to mimic the behavior of the PostgreSQL to_tsvector and to_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 and NpsgqlLargeObjectStream 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 and NpgsqlDbType.TimeTZ were obsoleted many releases ago, and were finally removed. Use NpgsqlDbType.TimestampTz and NpgsqlDbType.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