Prerequisites
- The .NET SDK 10. This guide was tested with .NET SDK 10.0.401, EF Core 10.0.12, and
Npgsql.EntityFrameworkCore.PostgreSQL10.0.3 (Npgsql 10.0.3). - A ClickHouse Cloud account
psql, to create the database. You can also run theCREATE DATABASEstatement in the SQL console.
Create a ClickHouse Managed Postgres service
In the ClickHouse Cloud console, click New service and select Postgres. The instance is ready in a few minutes. See the quickstart for a walkthrough.Get your connection details
Click Connect in the left sidebar of your service. Keep Directly selected, turn on Use SSL, and copy the password and the server hostname. Then click Download CA certificate. You can also download the certificate later from Settings → CA Certificate. It’s unique to your instance, so Npgsql can use it to verify that it’s talking to your server. This guide connects directly to Postgres on port5432, for both the app and EF Core migrations. An ASP.NET Core app is a long-lived process, and Npgsql already pools connections in it (up to 100 per connection string by default), so it doesn’t need a second pooler. If you run many instances of the app, see Use PgBouncer.
The Connect modal has no .NET tab, and Npgsql doesn’t accept the postgresql:// URL from the url tab. It uses Keyword=Value connection strings instead. Take the values from the URL and map them to Npgsql keywords:
Set up the project
Create an ASP.NET Core project, add the Npgsql EF Core provider and the EF Core design-time package, and install thedotnet ef command-line tool:
Configure the connection
Move the CA certificate you downloaded into the project directory and rename it toca-certificate.pem.
Create a database for the app. Replace <PASSWORD> and the host with the values from the Connect modal:
appsettings.json and source control:
Development environment, which dotnet run and dotnet ef use by default. In production, set the environment variable ConnectionStrings__TodoDb instead.
SSL Mode=VerifyFull makes Npgsql check that the server certificate is signed by the CA in Root Certificate and that it matches the hostname. Npgsql resolves a relative Root Certificate path against the current working directory, which is the project directory when you use dotnet run and dotnet ef. Use an absolute path if you start the app from somewhere else.
If the CA is wrong or missing, or the host doesn’t match the certificate (for example, when you connect by IP address), the connection fails with
Exception while performing SSL handshake and the inner error The remote certificate was rejected by the provided RemoteCertificateValidationCallback. If the Root Certificate file doesn’t exist, the inner error is Could not find file. Also note that:- Npgsql’s default is
SSL Mode=Prefer, andSSL Mode=Requiredoesn’t verify the certificate either, even when you setRoot Certificate. Always setSSL Mode=VerifyFull. - Npgsql reads the
PGSSLROOTCERTandPGPASSWORDenvironment variables, but notPGHOSTorPGSSLMODE. The variables from the env tab of the Connect modal therefore aren’t enough on their own.
Define the model
CreateTodo.cs with the entity class:
Todo.cs
TodoDb.cs with the DbContext:
TodoDb.cs
Program.cs. It registers TodoDb with the Npgsql provider and maps one endpoint per operation. In an existing app, you only need the AddDbContext call with UseNpgsql:
Program.cs
/db/tls endpoint reads pg_stat_ssl for the app’s own database session, so you can check that the connection is encrypted.
Create the schema
Generate a migration from the model and apply it:dotnet ef migrations add writes the migration to the Migrations folder, and dotnet ef database update applies it and records it in the __EFMigrationsHistory table. On the first run, EF Core logs a failed SELECT from __EFMigrationsHistory before it creates the table; you can ignore it.
Before applying migrations, EF Core takes a lock so that two deployments can’t migrate the same database at the same time. With Npgsql, the lock is a LOCK TABLE on __EFMigrationsHistory inside the migration transaction, not a session-level advisory lock, so it’s released when the transaction ends.
Run and verify
Start the app on port3115:
DELETE request returns 204 No Content, so its line is empty.
Check that the app’s database session uses TLS:
guide_dotnet and then public, and click the Todos table. EF Core uses the class and property names as quoted identifiers, so the table is "Todos" and the columns are "Id", "Title", and so on. The table contains the remaining row:
Use PgBouncer
Npgsql opens up to 100 connections per app instance (Maximum Pool Size). If many instances together approach the Postgres connection limit, connect the app through the bundled PgBouncer instead. Select via PgBouncer in the Connect modal, and change the port in the connection string to 6432:
- Prepared statements: EF Core doesn’t prepare statements, and Npgsql’s automatic preparation is off by default (
Max Auto Prepare=0), so queries use unnamed statements, which work through PgBouncer. Keep it that way. Through PgBouncer, prepared statements fromMax Auto PrepareorNpgsqlCommand.Preparefail intermittently with08P01: prepared statement did not exist(No Reset On Close=trueprevents this), and after a migration changes a column’s type, they keep failing withcached plan must not change result type, even on new connections. If you need prepared statements, connect directly. - Session state: settings made with
SET, includingsearch_path, don’t carry over between transactions. UseSET LOCALinside a transaction instead. PgBouncer rejects theSearch PathandOptionsconnection string keywords withunsupported startup parameter;TimezoneandApplication Namework. - Migrations:
dotnet ef database updatealso works through PgBouncer, because the migration lock is a table lock that ends with its transaction. - TLS check:
pg_stat_ssldescribes the connection between PgBouncer and Postgres, so/db/tlsreturnsnone. Your app’s connection to PgBouncer is still verified: a wrong CA certificate fails in the same way.
Next steps
- Connection: connection strings, PgBouncer, and TLS
- Settings: Postgres and PgBouncer parameters, such as
max_connections - Read replicas: send read-only queries to a replica
- Security: IP access lists and private networking