Table of Contents

Npgsql 7.0 Release Notes

Npgsql version 7.0 has been released and is available on nuget.

New features

The full list of issues for this release is available here.

DbDataSource

A major improvement in Npgsql 7.0 is NpgsqlDataSource, which implements the new DbDataSource abstraction in .NET System.Data. A data source represents your PostgreSQL database, and can hand out connections to it or support direct execution of SQL to it.

Instead of directly instantiating an NpgsqlConnection and then executing commands against it, you now create a data source once, and then use that throughout your application:

await using var dataSource = NpgsqlDataSource.Create(connectionString);

// Execute a command directly against the data source, no NpgsqlConnection needed:
await using var command = dataSource.CreateCommand("INSERT INTO some_table (some_field) VALUES (8)");
await command.ExecuteNonQueryAsync();

// Open a connection in order to e.g. start a transaction on it:
await using var connection = await dataSource.OpenConnectionAsync();

Since the data source encapsulates all the necessary configuration for connecting to a database (e.g. the connection string, authentication callbacks...), it can be registered in dependency injection or passed around as needed, without needing any additional information. The new NpgsqlDataSourceBuilder also provides the ideal API point for various configuration when building a data source:

var dataSourceBuilder = new NpgsqlDataSourceBuilder("Host=localhost;Username=test;Password=test");
dataSourceBuilder
    .UseLoggerFactory(loggerFactory) // Configure logging
    .UsePeriodicPasswordProvider() // Automatically rotate the password periodically
    .UseNodaTime(); // Use NodaTime for date/time types
await using var dataSource = dataSourceBuilder.Build();

If you're using dependency injection (e.g. in an ASP.NET application), see the Npgsql.DependencyInjection package for an easy way to set up your data source.

Improved logging with Microsoft.Extensions.Logging

Previous versions had a custom logging implementation which required special adapters and was hard to use. Npgsql 7.0 fully supports the standard .NET Microsoft.Extensions.Logging - just provide Npgsql with your ILoggerFactory and you're ready to go.

If you using ASP.NET, things are even easier with the new Npgsql.DependencyInjection, which takes care of seamlessly picking up the ASP.NET logging configuration from DI:

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
builder.Services.AddNpgsqlDataSource("Host=localhost;Username=test;Password=test");

For more details, see the updated logging documentation page.

Support for version 3 of the logical replication protocol

PostgreSQL 15 introduced improvements to logical replication, in particular around streaming large, in-progress transactions. Npgsql 7.0 adds support for improvements - see #4216 for more information (thanks @Brar!).

Breaking changes

CommandType.StoredProcedure now invokes procedures instead of functions

When NpgsqlCommand.CommandType is set to CommandType.StoredProcedure, Npgsql now generates SQL for invoking a PostgreSQL stored procedure, and not a function, as before. To opt out of this breaking change and continue to invoke functions as before, enable the Npgsql.EnableStoredProcedureCompatMode AppContext switch as follows:

AppContext.SetSwitch("Npgsql.EnableStoredProcedureCompatMode", true);

For context, PostgreSQL originally only supported functions, and did not support the standard SQL concept of stored procedures; because of this, CommandType.StoredProcedure was implemented to invoke functions. PostgreSQL 11 then introduced stored procedures, which have various advantages over functions in some scenarios (e.g. the ability to use transactions). The 7.0 release changes CommandType.StoredProcedure to invoke procedures as its naming suggests, and aligns Npgsql with other database providers for better compatibility.

Note that with Npgsql, there is no advantage in using CommandType.StoredProcedure over simply invoking your function or procedure via SQL. Doing so is in fact recommended:

// Invoke a procedure
using var command1 = new NpgsqlCommand("CALL some_procedure($1, $2)", connection)
{
    new() { Value = "some_value" },
    new() { Value = "some_other_value" }
};

// Invoke a function
using var command2 = new NpgsqlCommand("SELECT * FROM some_function($1, $2)", connection)
{
    new() { Value = "some_value" },
    new() { Value = "some_other_value" }
};

For more information on calling procedures and functions, see this doc section.

Managing type mappings at the connection level is no longer supported

Previous versions of Npgsql allowed mapping custom types (enums/composites) and configuring plugins (NetTopologySuite, NodaTime) at the connection level; the type mapping change would persist only for the lifetime of the connection, and would be reverted when the connection closed. This mechanism was inefficient - connections get opened and closed a lot - and added significant maintenance burden internally.

With the introduction of NpgsqlDataSource, Npgsql now has a natural API point for managing type mappings:

var dataSourceBuilder = new NpgsqlDataSourceBuilder("Host=localhost;Username=test;Password=test");
dataSourceBuilder.MapEnum<MyEnum>();
dataSourceBuilder.UseNodaTime();
await using var dataSource = dataSourceBuilder.Build();

All connections handed out by the data source will use the configured type mappings.

Note that managing type mappings globally via NpgsqlConnection.GlobalTypeMapper is supported as before, but has been marked as obsolete; although we do not plan on removing global type mappings any time soon, NpgsqlDataSourceBuilder is now the recommended way to manage type mappings.

Global type mappings must now be done before any usage

Previously, any type mapping configuration done via NpgsqlConnection.GlobalTypeMapper would take effect for any new connection opened after the change. Starting with 7.0, global type mappings must be done before the data source or pool is created. To ensure correct functioning, do any global type mappings at the very start of your program, before using any other Npgsql API.

NpgsqlDataReader.Dispose no longer swallows exceptions

Previously, when an unconsumed NpgsqlDataReader was disposed, any exceptions that occurred while consuming the remaining results were swallowed and Dispose completed successfully. These exceptions are no longer swallowed, and are thrown from Dispose. This also affects scenarios where NpgsqlDataReader is used in a C# using statement.

In most cases in .NET, throwing from Dispose is discouraged. If the instance being disposed is used in a using statement and some exception is thrown, that exception triggers Dispose being called (as per using), and if Dispose throws an exception of its own, then that exception bubbles up. As a result, the original exception is hidden, making it difficult to understand exactly what happened. As a result, it is common for Dispose to catch any exceptions internally and swallow them, allowing the original exception to bubble up.

However, in the Npgsql case, swallowing exceptions in Dispose can have very problematic consequences. A reader can be disposed before it's entirely consumed, after only part of the result set has been read; when this happens, Npgsql consumes the rest of the result set as part of the disposal. If any errors occur past this point, then those exceptions were previously swallowed; this meant that the application could be unaware that part of the command failed, since no exception was raised. Throwing these exceptions from dispose ensures that the application is aware of any failures that occurred.

For more context on this change, see the discussion in #4377.

The logging API has been replaced by Microsoft.Extensions.Logging

Npgsql previously had its own logging API, requiring special adapters to the standard logging libraries. This API has been removed in 7.0, and replaced with support for the standard Microsoft.Extensions.Logging package. See Logging for more information.

The obsoleted NpgsqlDateTime, NpgsqlDate and NpgsqlTimeSpan have been removed

NpgsqlDateTime, NpgsqlDate and NpgsqlTimeSpan were "provider-specific" types, designed to expose the full range of the PostgreSQL date/time types, which can represent values beyond the built-in .NET types (e.g. DateTime). However, these types were problematic in many ways, and were seldom used. The types were obsoleted in Npgsql 6.0, and have been removed in 7.0.

To deal with date/time values outside the range of the corresponding .NET types, see Date and Time Handling.

NpgsqlConnection.Settings has been removed

The connection's connection string is still exposed via NpgsqlConnection.ConnectionString; this can be parsed with NpgsqlConnectionStringBuilder.

Replication APIs now return UTC DateTime

Previously, replication APIs returned DateTime instances of Kind Unspecified; this has been changed to Utc to reflect the actual type of data sent by PostgreSQL.

Contributors

Thank you very much to the following people who have contributed to the individual 7.0.x. releases.

Milestone 7.0.6

Contributor Assigned issues
@vonzshik 10
@roji 1

Milestone 7.0.4

Contributor Assigned issues
@vonzshik 3
@roji 2

Milestone 7.0.2

Contributor Assigned issues
@vonzshik 5
@manandre 2
@roji 1

Milestone 7.0.1

Contributor Assigned issues
@roji 3
@Brar 1
@vonzshik 1

Milestone 7.0.0

Contributor Assigned issues
@roji 37
@Brar 9
@vonzshik 4
@0xced 1
@baal2000 1