Skip to content

Integration Npgsql Extension

aryehcitron@gmail.com edited this page Nov 7, 2026 · 12 revisions

The Kronikol.Extensions.Npgsql package adds PostgreSQL operation tracking to your test diagrams via Npgsql's built-in DiagnosticSource instrumentation. No code changes required in production — just install and configure in your test project.

Zero production changes. Npgsql emits diagnostic events automatically. This extension subscribes to them globally, so every NpgsqlConnection in the process is tracked without any wrapping or decoration.


How It Works

Npgsql publishes DiagnosticSource events under the listener name "Npgsql". The NpgsqlDiagnosticTracker subscribes to these events and correlates BeforeExecuteCommand / AfterExecuteCommand pairs using ExecutionId (a GUID extracted via reflection). Each pair is classified by UnifiedSqlClassifier and logged to RequestResponseLogger.

Because it logs to the same RequestResponseLogger as the standard TestTrackingMessageHandler, SQL operations appear alongside your HTTP API calls in the same sequence diagram.


Install

dotnet add package Kronikol.Extensions.Npgsql

Client versions

The package asks for Npgsql 10.0.0 or later, so a suite can pin whichever 10.x from 10.0.0 its application ships with. The package's own suite runs on the newest 10.x, and in CI on 10.0.0 as well. A project that takes the client only through this package resolves 10.0.0; reference the client yourself for a newer one. The floor is 10.0.0, the first of the major, where the suite passes. Before 5.27.12 the package asked for 10.0.2.

Setup

Option A — Dependency Injection

// In your test WebApplicationFactory ConfigureTestServices:
services.AddPostgreSqlTestTracking(options =>
{
    options.Verbosity = SqlTrackingVerbosityLevel.Detailed;
    // options.LogSqlText = true;
    // options.LogParameters = true;
});

Option B — Static (No DI)

// In your test setup (e.g. constructor or OneTimeSetUp):
NpgsqlTestTracking.EnsureTracking(new NpgsqlTrackingOptions
{
    Verbosity = SqlTrackingVerbosityLevel.Detailed
});

// In teardown:
NpgsqlTestTracking.Reset();

Configuration

Property Default Description
ServiceName "PostgreSQL" The participant name shown in diagrams
CallerName "Caller" The caller participant name
Verbosity Detailed Raw, Detailed, or Summarised
LogSqlText true Include full SQL text in Detailed mode
LogParameters false Include parameter values
DependencyCategory "PostgreSQL" Controls participant shape/colour
UriScheme "postgresql" URI scheme in diagram URIs
CurrentTestInfoFetcher null Returns the current test's name and ID. Required for wrapping approach
HttpContextAccessor null Optional — enables dual-resolution of test identity from HTTP headers
ExcludedOperations [] Operations to exclude from tracking
SetupVerbosity null Verbosity override for the Setup phase. See Phase-Aware Tracking
ActionVerbosity null Verbosity override for the Action phase. See Phase-Aware Tracking
TrackDuringSetup true Track during test setup phase
TrackDuringAction true Track during test action phase
LogResponseContent true Include response data in diagram arrows at all verbosity levels (v2.37.0+)
MaxResponseRows 10 Maximum rows to capture in response content
MaxValueDisplayLength 500 Truncate individual cell values beyond this length
ResponseDetail (follows verbosity) RowCountOnly, RowCountAndColumns, or FullRows; unset = FullRows at Raw/Detailed, RowCountAndColumns at Summarised

All options inherit from SqlTrackingOptionsBase in the core package.


Verbosity Levels

Level Arrow label URI
Raw Full SQL text postgresql://host/database
Detailed SELECT FROM Users postgresql://host/database/Users
Summarised SELECT postgresql:///database/Users

Comments and string literals are ignored when picking the table (3.0.83+). The table name is extracted by regular expression, and the FROM/INTO extractors match anywhere in the statement, so a comment or a literal that mentions those words used to win: a query carrying -- rolled up from the weekly aggregate was reported as reading a table called the. A statement that opened with a comment was worse: it classified as Other with no table at all, and at Summarised verbosity such a call was dropped from the report. -- comments, /* */ comments and '…' literals are now removed before classification. Quoted identifiers (double-quoted, bracketed and backticked) are left alone, and the SQL the report displays is unchanged — this affects classification only.


Classified Operations

Uses UnifiedSqlClassifier — supports SELECT, INSERT, UPDATE, DELETE, MERGE, UPSERT (ON CONFLICT DO UPDATE), stored procedures (EXEC/CALL), DDL (CREATE/ALTER/DROP TABLE), TRUNCATE, and transactions. Multi-dialect: handles PostgreSQL double-quotes, brackets, backticks, CTEs, SET prefix stripping, and Spanner hints.


Response Payload Capture (v2.37.0+)

Response arrows in diagrams now show actual data instead of being empty. The ResponseDetail option controls the level of detail:

ResponseDetail Example output
RowCountOnly 3 rows
RowCountAndColumns (default at Summarised verbosity) 3 rows [Name, Preference, CreatedAt]
FullRows (default at Raw/Detailed verbosity) JSON row preview (up to MaxResponseRows)

With LogResponseContent = false a query's or a scalar's response arrow has no note, and a command that changed rows still says how many, except at Summarised verbosity.

A statement that fails is drawn with its error (since 4.4.1): a response arrow with status Error, with the exception's message as its note, and the exception reaches your code unchanged. A COMMIT or ROLLBACK that fails is drawn the same way. Before 4.4.1 a failed statement left a request with no response, and a failed commit or rollback drew nothing.

The reader your code gets behaves as the provider's own (since 5.11.1): every member goes to the provider's reader, so GetFieldValue<T> returns the provider's conversion (a DateTimeOffset or a List<int> on Npgsql, a DateOnly on SQL Server), and a reader opened with CommandBehavior.SequentialAccess, as Dapper opens every query, is recorded as your code reads it, never ahead of it. Before 5.11.1 every Dapper query failed on SqlClient and Npgsql for that reason. A command with several result sets has one note per set, each on its own line, and a cell holding a provider's own type (a geography, a range) shows its text. Nothing the wrapper does while it records a command can fail the command: a failure is caught, and the run's diagnostics name the command it could not record. A data source that is not a host ((localdb)\MSSQLLocalDB, host,1433, a named instance, a pipe) is drawn against a host made from it (localdb-mssqllocaldb, host:1433), where before it failed every command with a UriFormatException.


Wrapping Alternative (v2.37.0+)

In addition to the zero-change DiagnosticSource approach above, the Npgsql extension also provides a connection-wrapping approach. This is useful when DiagnosticSource instrumentation is unavailable, disabled, or when you need response payload capture from ExecuteReader/ExecuteScalar.

Classes

Class Wraps Purpose
TrackingNpgsqlConnection NpgsqlConnection Intercepts command creation and transaction begin
TrackingNpgsqlCommand NpgsqlCommand Intercepts ExecuteReader, ExecuteNonQuery, ExecuteScalar (sync + async)
TrackingNpgsqlTransaction NpgsqlTransaction Logs BEGIN, COMMIT, ROLLBACK

Extension Method

using Npgsql;
using Kronikol.Extensions.Npgsql;

var connection = new NpgsqlConnection(connectionString)
    .WithTestTracking(new NpgsqlTrackingOptions
    {
        ServiceName = "PostgreSQL",
        Verbosity = SqlTrackingVerbosityLevel.Detailed,
        CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
    });

NpgsqlConnection.WithTestTracking() returns a TrackingNpgsqlConnection (which extends DbConnection), so it works transparently with any code that accepts DbConnection — including Dapper, raw ADO.NET, and DI registrations.

DI Registration

builder.ConfigureTestServices(services =>
{
    services.AddScoped<DbConnection>(sp =>
    {
        var inner = new NpgsqlConnection(connectionString);
        return inner.WithTestTracking(new NpgsqlTrackingOptions
        {
            ServiceName = "PostgreSQL",
            Verbosity = SqlTrackingVerbosityLevel.Detailed,
            CurrentTestInfoFetcher = CurrentTestInfo.Fetcher
        });
    });
});

DiagnosticSource vs Wrapping: The DiagnosticSource approach (Option A/B above) requires zero production code changes but cannot capture response payloads. The wrapping approach captures response data from ExecuteReader and ExecuteScalar, but requires the connection to be wrapped. Choose based on your needs — you can use both in the same project for different connections.

A call that fails

A statement, a commit or a rollback that fails is answered Error, with the outer message as its note and, from 5.34.3, the message chain in error (#232). A reader that fails partway is answered Error with the reason, where it was answered OK at Close with the rows read so far. Every answer is filed under the test and phase its call started in, where the answer read them again when it came. History's older rules still read the call as the release before recorded it, so a suite that upgrades reads no change there (Cross-Run History).

Home · 🔍 Search this wiki Full-text search across every page. The “Pages” list above matches page and heading names, not page content.


Demo


Getting Started

Common Tasks

Integration Guides

Uninstrumentable / polyglot backends

Extensions

Configuration

Features

Reference

Clone this wiki locally