前置条件
- .NET SDK 10。本指南已在 .NET SDK 10.0.401、EF Core 10.0.12 和
Npgsql.EntityFrameworkCore.PostgreSQL10.0.3 (Npgsql 10.0.3) 环境下测试通过。 - 一个 ClickHouse Cloud 账户
psql,用于创建数据库。您也可以在 SQL 控制台中运行CREATE DATABASE语句。
创建 ClickHouse Managed Postgres 服务
在 ClickHouse Cloud 控制台中,点击 New service,然后选择 Postgres。实例会在几分钟内就绪。详细操作步骤请参阅快速入门。获取连接信息
在服务的左侧边栏中点击 Connect。保持选中 Directly,开启 Use SSL,然后复制密码和服务器主机名。接着点击 Download CA certificate。您也可以稍后在 Settings → CA Certificate 中下载该证书。该证书为您的实例所独有,因此 Npgsql 可以借此验证所连接的确实是您的服务器。 在本指南中,应用和 EF Core 迁移均通过端口5432 直接连接到 Postgres。ASP.NET Core 应用是长期运行的进程,Npgsql 已在其中提供连接池 (默认每个连接字符串最多 100 个连接) ,因此无需再额外使用连接池器。如果您运行了大量应用实例,请参阅使用 PgBouncer。
Connect 弹窗中没有 .NET 选项卡,而且 Npgsql 不接受 url 选项卡中的 postgresql:// URL,而是使用 Keyword=Value 形式的连接字符串。请从 URL 中提取各项值,并将其对应到 Npgsql 关键字:
设置项目
创建一个 ASP.NET Core 项目,添加 Npgsql EF Core 提供程序和 EF Core 设计时包,并安装dotnet ef 命令行工具:
配置连接
将下载的 CA 证书移动到项目目录中,并重命名为ca-certificate.pem。
为应用创建一个数据库。将 <PASSWORD> 和主机地址替换为 Connect 弹窗中显示的值:
appsettings.json 中或被提交到源代码管理系统:
Development 环境中读取用户机密信息,而 dotnet run 和 dotnet ef 默认就使用该环境。在生产环境中,请改为设置环境变量 ConnectionStrings__TodoDb。
设置 SSL Mode=VerifyFull 后,Npgsql 会检查服务器证书是否由 Root Certificate 中指定的 CA 签发,以及该证书是否与主机名匹配。对于相对形式的 Root Certificate 路径,Npgsql 会基于当前工作目录进行解析;使用 dotnet run 和 dotnet ef 时,当前工作目录即为项目目录。如果从其他位置启动应用,请使用绝对路径。
如果 CA 不正确或缺失,或者主机与证书不匹配 (例如通过 IP 地址连接时) ,连接将失败并报错
Exception while performing SSL handshake,内部错误为 The remote certificate was rejected by the provided RemoteCertificateValidationCallback。如果 Root Certificate 文件不存在,内部错误则为 Could not find file。另请注意:- Npgsql 的默认值为
SSL Mode=Prefer,而SSL Mode=Require同样不会验证证书,即使设置了Root Certificate也是如此。请始终设置SSL Mode=VerifyFull。 - Npgsql 会读取
PGSSLROOTCERT和PGPASSWORD环境变量,但不会读取PGHOST或PGSSLMODE。因此,仅靠 Connect 弹窗中 env 选项卡提供的变量是不够的。
定义模型
创建Todo.cs,并在其中定义实体类:
Todo.cs
TodoDb.cs 文件,并在其中定义 DbContext:
TodoDb.cs
Program.cs 的内容替换为以下代码。这段代码通过 Npgsql 提供程序注册 TodoDb,并为每个操作映射一个端点。如果是现有应用,只需添加使用 UseNpgsql 的 AddDbContext 调用即可:
Program.cs
/db/tls 端点会查询应用自身数据库会话的 pg_stat_ssl,以便您确认该连接已加密。
创建 schema
根据模型生成迁移文件并执行:dotnet ef migrations add 会将迁移写入 Migrations 文件夹,dotnet ef database update 则会应用该迁移,并将其记录到 __EFMigrationsHistory 表中。首次运行时,EF Core 在创建该表之前,会记录一条从 __EFMigrationsHistory 执行 SELECT 失败的日志,可直接忽略。
在应用迁移之前,EF Core 会先获取一个锁,以防两个部署同时迁移同一个数据库。使用 Npgsql 时,该锁是在迁移事务内对 __EFMigrationsHistory 执行的 LOCK TABLE,而非会话级的咨询锁,因此事务结束时锁即被释放。
运行与验证
在端口3115 上启动应用:
DELETE 请求返回 204 No Content,因此对应的行为空。
检查应用的数据库会话是否使用 TLS:
guide_dotnet 和 public,然后点击 Todos 表。EF Core 会将类名和属性名作为带引号的标识符,因此表名为 "Todos",列名为 "Id"、"Title" 等。表中保留了剩下的这一行:
使用 PgBouncer
Npgsql 为每个应用实例最多打开 100 个连接 (Maximum Pool Size) 。如果多个实例的连接总数接近 Postgres 的连接上限,请改为通过内置的 PgBouncer 连接应用。在 Connect 弹窗中选择 via PgBouncer,并将连接字符串中的端口改为 6432:
- 预处理语句: EF Core 不会预处理语句,而 Npgsql 的自动预处理默认处于关闭状态 (
Max Auto Prepare=0) ,因此查询使用的是未命名语句,可以通过 PgBouncer 正常工作。请保持此配置。通过 PgBouncer 时,由Max Auto Prepare或NpgsqlCommand.Prepare创建的预处理语句会间歇性失败,并报错08P01: prepared statement did not exist(设置No Reset On Close=true可避免此问题) ;而在迁移更改了某列的类型之后,即使使用新连接,它们也会持续报错cached plan must not change result type。如需使用预处理语句,请直接连接。 - 会话状态: 通过
SET进行的设置 (包括search_path) 不会在事务之间保留,请改为在事务内使用SET LOCAL。PgBouncer 会拒绝Search Path和Options连接字符串关键字,并报错unsupported startup parameter;Timezone和Application Name则可以正常使用。 - 迁移:
dotnet ef database update同样可以通过 PgBouncer 运行,因为迁移锁是表锁,会随所在事务的结束而释放。 - TLS 检查:
pg_stat_ssl反映的是 PgBouncer 与 Postgres 之间的连接,因此/db/tls会返回none。应用程序到 PgBouncer 的连接仍会经过验证:如果使用了错误的 CA 证书,连接同样会失败。