ClickHouse C# 客户端
用于连接 ClickHouse 的官方 C# 客户端。 客户端源代码托管在 GitHub 仓库 中。 最初由 Oleg V. Kozlyuk 开发。
该库提供两个主要 API:
-
ClickHouseClient(推荐):一个为单例使用设计的高级、线程安全客户端。提供用于查询和批量插入的简单异步 API。适用于大多数应用程序。 -
ADO.NET(
ClickHouseDataSource、ClickHouseConnection、ClickHouseCommand):标准 .NET 数据库抽象。用于 ORM 集成(Dapper、Linq2db)以及在需要与 ADO.NET 兼容的场景中。ClickHouseBulkCopy是一个辅助类,用于通过 ADO.NET 连接高效插入数据。ClickHouseBulkCopy已被弃用,并将在未来版本中移除;请改用ClickHouseClient.InsertBinaryAsync。
这两种 API 共享相同的底层 HTTP 连接池,并且可以在同一个应用程序中同时使用。
迁移指南
- 在
.csproj文件中将包名更新为ClickHouse.Driver,并将版本更新为 NuGet 上的最新版本。 - 将代码库中所有对
ClickHouse.Client的引用更新为ClickHouse.Driver。
支持的 .NET 版本
ClickHouse.Driver 支持以下 .NET 版本:
- .NET 6.0
- .NET 8.0
- .NET 9.0
- .NET 10.0
安装
从 NuGet 安装该软件包:
或者使用 NuGet 包管理器:
快速入门
配置
配置与 ClickHouse 的连接有两种方式:
- 连接字符串: 以分号分隔的键值对,用于指定主机、身份验证凭据以及其他连接选项。
ClickHouseClientSettings对象: 强类型的配置对象,可以从配置文件加载或在代码中设置。
下面是所有配置项的完整列表,包括它们的默认值及其作用。
连接设置
| 属性 | 类型 | 默认值 | 连接字符串键 | 描述 |
|---|---|---|---|---|
| Host | string | "localhost" | Host | ClickHouse 服务器的主机名或 IP 地址 |
| Port | ushort | 8123 (HTTP) / 8443 (HTTPS) | Port | 端口号;默认值取决于协议 |
| Username | string | "default" | Username | 身份验证用户名 |
| Password | string | "" | Password | 身份验证密码 |
| Database | string | "" | Database | 默认数据库;为空时使用服务器/用户的默认值 |
| Protocol | string | "http" | Protocol | 连接协议:"http" 或 "https" |
| Path | string | null | Path | 用于反向代理场景的 URL 路径(例如 /clickhouse) |
| Timeout | TimeSpan | 2 分钟 | Timeout | 操作超时时间(在连接字符串中以秒为单位存储) |
数据格式与序列化
| 属性 | 类型 | 默认值 | 连接字符串键名 | 描述 |
|---|---|---|---|---|
| UseCompression | bool | true | Compression | 为数据传输启用 gzip 压缩 |
| UseCustomDecimals | bool | true | UseCustomDecimals | 使用 ClickHouseDecimal 处理任意精度小数;如果为 false,则使用 .NET decimal(128 位上限) |
| ReadStringsAsByteArrays | bool | false | ReadStringsAsByteArrays | 将 String 和 FixedString 列读取为 byte[] 而不是 string;适用于二进制数据 |
| UseFormDataParameters | bool | false | UseFormDataParameters | 将参数以表单数据的形式发送,而不是作为 URL 查询字符串 |
| JsonReadMode | JsonReadMode | Binary | JsonReadMode | JSON 数据的返回方式:Binary(返回 JsonObject)或 String(返回原始 JSON 字符串) |
| JsonWriteMode | JsonWriteMode | String | JsonWriteMode | JSON 数据的发送方式:String(通过 JsonSerializer 序列化,接受所有输入)或 Binary(仅支持带类型提示的已注册 POCO) |
会话管理
| 属性 | 类型 | 默认值 | 连接字符串键 | 描述 |
|---|---|---|---|---|
| UseSession | bool | false | UseSession | 启用有状态会话;将请求串行执行 |
| SessionId | string | null | SessionId | 会话 ID;如果为 null 且 UseSession 为 true,则自动生成 GUID |
UseSession 选项启用服务器会话持久化,从而可以使用 SET 语句和临时表。会话在 60 秒无活动后将被重置(默认超时时间)。可以通过在 ClickHouse 语句中设置会话相关配置或通过服务器配置来延长会话生命周期。
ClickHouseConnection 类通常允许并行操作(多个线程可以并发执行查询)。但是,启用 UseSession 选项后,在任意时刻,每个连接只能有一个活动查询(这是服务器端限制)。
安全
| 属性 | 类型 | 默认值 | 连接字符串键 | 说明 |
|---|---|---|---|---|
| SkipServerCertificateValidation | bool | false | — | 跳过 HTTPS 证书验证;不应用于生产环境 |
HTTP 客户端配置
| 属性 | 类型 | 默认值 | 连接字符串键 | 说明 |
|---|---|---|---|---|
| HttpClient | HttpClient | null | — | 自定义的预配置 HttpClient 实例 |
| HttpClientFactory | IHttpClientFactory | null | — | 用于创建 HttpClient 实例的自定义工厂 |
| HttpClientName | string | null | — | 供 HttpClientFactory 在创建特定客户端时使用的名称 |
日志与调试
| 属性 | 类型 | 默认值 | 连接字符串键 | 描述 |
|---|---|---|---|---|
| LoggerFactory | ILoggerFactory | null | — | 用于诊断日志记录的 LoggerFactory 工厂 |
| EnableDebugMode | bool | false | — | 启用 .NET 网络跟踪(需要配置 LoggerFactory 且日志级别设置为 Trace);对性能有显著影响 |
自定义设置与角色
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| CustomSettings | IDictionary<string, object> | Empty | set_* prefix | ClickHouse 服务器设置,参见下方说明 |
| Roles | IReadOnlyList<string> | Empty | Roles | 用逗号分隔的 ClickHouse 角色(例如:Roles=admin,reader) |
使用 connection string 配置自定义设置时,请使用 set_ 前缀,例如 set_max_threads=4。使用 ClickHouseClientSettings 对象时,不要使用 set_ 前缀。
有关可用设置的完整列表,请参见此处。
连接字符串示例
基本连接
使用自定义 ClickHouse 配置
QueryOptions
QueryOptions 允许你在每个查询的基础上覆盖客户端级别的设置。所有属性都是可选的,只有在显式指定时才会覆盖客户端默认值。
| Property | Type | Description |
|---|---|---|
| QueryId | string | 用于在 system.query_log 中进行跟踪或取消操作的自定义查询标识符 |
| Database | string | 覆盖此查询的默认数据库 |
| Roles | IReadOnlyList<string> | 覆盖此查询的客户端角色 |
| CustomSettings | IDictionary<string, object> | 此查询的 ClickHouse 服务器设置(例如 max_threads) |
| CustomHeaders | IDictionary<string, string> | 此查询的附加 HTTP 头部 |
| UseSession | bool? | 覆盖此查询的会话行为 |
| SessionId | string | 此查询的会话 ID(需要 UseSession = true) |
| BearerToken | string | 覆盖此查询的身份验证令牌 |
| MaxExecutionTime | TimeSpan? | 服务器端查询超时时间(作为 max_execution_time 设置传递);若超时则由服务器取消查询 |
示例:
InsertOptions
InsertOptions 扩展了 QueryOptions,用于配置通过 InsertBinaryAsync 执行批量插入操作的特定设置。
| Property | Type | Default | Description |
|---|---|---|---|
| BatchSize | int | 100,000 | 每个批次包含的行数 |
| MaxDegreeOfParallelism | int | 1 | 并行上传的批次数量 |
| Format | RowBinaryFormat | RowBinary | 二进制格式:RowBinary 或 RowBinaryWithDefaults |
所有 QueryOptions 的属性在 InsertOptions 中同样可用。
示例:
ClickHouseClient
ClickHouseClient 是与 ClickHouse 交互的推荐 API。它是线程安全的,适合作为单例使用,并在内部管理 HTTP 连接池。
创建客户端
通过连接字符串或 ClickHouseClientSettings 对象创建一个 ClickHouseClient 实例。有关可用选项,请参见 Configuration 部分。
ClickHouse Cloud 服务的连接信息可以在 ClickHouse Cloud 控制台中获取。
选择某个服务并单击 Connect:

选择 C#,连接信息会显示在下方。

如果您使用的是自管理 ClickHouse,则连接信息由您的 ClickHouse 管理员配置。
使用连接字符串:
也可以使用 ClickHouseClientSettings:
在依赖注入场景中,请使用 IHttpClientFactory:
ClickHouseClient 被设计为拥有较长的生命周期,并在整个应用程序中共享使用。通常只需创建一次(通常作为单例),并在所有数据库操作中复用该实例。客户端在内部管理 HTTP 连接池。
执行查询
使用 ExecuteNonQueryAsync 来执行不返回结果的语句:
使用 ExecuteScalarAsync 检索单个值:
插入数据
参数化插入
通过 ExecuteNonQueryAsync 使用参数化查询插入数据。必须在 SQL 中使用 {name:Type} 语法指定参数类型:
批量插入
使用 InsertBinaryAsync 可高效插入大量行。它使用 ClickHouse 的原生行二进制格式来流式传输数据,支持并行批量上传,并避免在使用参数化查询时可能出现的“URL too long”错误。
对于大型数据集,可以通过 InsertOptions 配置批处理和并行度:
- 客户端在插入前会通过
SELECT * FROM <table> WHERE 1=0自动获取表结构。提供的值必须与目标列类型匹配。 - 当
MaxDegreeOfParallelism > 1时,批量数据会被并行上传。Session 与并行插入不兼容;要么禁用 Session,要么将MaxDegreeOfParallelism = 1。 - 如果希望服务端为未提供的列应用 DEFAULT 默认值,请在
InsertOptions.Format中使用RowBinaryFormat.RowBinaryWithDefaults。
读取数据
使用 ExecuteReaderAsync 执行 SELECT 查询。返回的 ClickHouseDataReader 通过 GetInt64()、GetString() 和 GetFieldValue<T>() 等方法,为结果列提供类型化访问。
调用 Read() 以移动到下一行。当没有更多行时,它返回 false。可以通过索引(从 0 开始)或列名访问列。
SQL 参数
在 ClickHouse 中,SQL 查询参数的标准格式为 {parameter_name:DataType}。
示例:
SQL bind 参数作为 HTTP URI 查询参数传递,因此如果使用过多,可能会触发 “URL too long” 异常。使用 InsertBinaryAsync 进行批量数据插入可以避免此限制。
查询 ID
每个查询都会被分配一个唯一的 query_id,可用于从 system.query_log 表中获取数据或取消长时间运行的查询。可以通过 QueryOptions 指定自定义的查询 ID:
如果你要指定自定义的 QueryId,请确保它在每次调用中都是唯一的。使用随机 GUID 是一个不错的选择。
原始数据流
使用 ExecuteRawResultAsync 可以以特定格式直接对查询结果进行流式传输,从而绕过数据读取器。这对于将数据导出为文件或透传到其他系统非常有用:
常见格式:JSONEachRow、CSV、TSV、Parquet、Native。有关所有可用选项,请参阅格式文档。
原始流插入
使用 InsertRawStreamAsync 将数据直接从文件或内存流中插入,格式可以是 CSV、JSON、Parquet,或任意ClickHouse 支持的格式。
从 CSV 文件插入:
有关用于控制数据摄取行为的选项,请参阅格式设置文档。
更多示例
有关更多实用示例,请参阅 GitHub 仓库中的 examples 目录。
ADO.NET
该库通过 ClickHouseConnection、ClickHouseCommand 和 ClickHouseDataReader 提供完整的 ADO.NET 支持。此 API 适用于与 ORM(Dapper、Linq2db)集成,以及需要标准 .NET 数据库抽象的场景。
使用 ClickHouseDataSource 进行生命周期管理
始终通过 ClickHouseDataSource 创建连接,以确保正确的生命周期管理和连接池使用。该 DataSource 在内部管理单个 ClickHouseClient,所有连接共享其 HTTP 连接池。
用于依赖注入:
在生产代码中不要直接创建 ClickHouseConnection。每次直接实例化都会创建一个新的 HTTP 客户端和连接池,在高负载下可能导致套接字资源耗尽:
相反,而是应始终使用 ClickHouseDataSource 或共享单个 ClickHouseClient 实例。
使用 ClickHouseCommand
基于连接创建命令来执行 SQL:
命令执行方法:
ExecuteNonQueryAsync()- 用于执行 INSERT、UPDATE、DELETE 以及 DDL 语句ExecuteScalarAsync()- 返回第一行的第一列ExecuteReaderAsync()- 返回一个用于遍历结果集的ClickHouseDataReader
使用 ClickHouseDataReader
ClickHouseDataReader 提供对查询结果的类型安全访问:
最佳实践
连接生命周期与连接池
ClickHouse.Driver 在底层使用 System.Net.Http.HttpClient。HttpClient 会针对每个端点维护一个连接池。因此:
- 数据库会话会通过由连接池管理的 HTTP 连接进行复用。
- HTTP 连接会由连接池自动回收。
- 即使
ClickHouseClient或ClickHouseConnection对象已被释放,连接依然可能保持存活。
推荐模式:
| 场景 | 推荐方式 |
|---|---|
| 通用场景 | 使用单例 ClickHouseClient |
| ADO.NET / ORM | 使用 ClickHouseDataSource(创建共享同一连接池的连接) |
| DI 环境 | 将 ClickHouseClient 或 ClickHouseDataSource 作为单例,并结合 IHttpClientFactory 注册 |
在使用自定义 HttpClient 或 HttpClientFactory 时,确保将 PooledConnectionIdleTimeout 设置为小于服务器 keep_alive_timeout 的值,以避免由于半关闭的连接导致的错误。Cloud 部署的默认 keep_alive_timeout 是 10 秒。
避免在没有共享 HttpClient 的情况下创建多个 ClickHouseClient 或独立的 ClickHouseConnection 实例。每个实例都会创建自己的连接池。
DateTime 处理
-
尽可能使用 UTC。 将时间戳存储为
DateTime('UTC')列,并在代码中使用DateTimeKind.Utc,以消除时区歧义。 -
使用
DateTimeOffset进行显式时区处理。 它始终表示一个确切的时间点,并包含偏移量信息。 -
在 SQL 类型提示中指定时区。 当向非 UTC 的列传递带有
UnspecifiedDateTime 值的参数时,在 SQL 中包含时区信息:
异步插入
异步插入 将批处理的职责从客户端转移到服务器。服务器无需客户端进行批处理,而是自行对传入数据进行缓冲,并根据可配置阈值将其写入存储。这对于高并发场景(例如可观测性负载中有许多代理程序发送小体量数据的情况)非常有用。
可以通过 CustomSettings 或连接字符串启用异步插入:
两种模式(由 wait_for_async_insert 控制):
| 模式 | 行为 | 使用场景 |
|---|---|---|
wait_for_async_insert=1 | 在数据刷写到磁盘后才返回 INSERT。错误会返回给客户端。 | 推荐用于大多数工作负载 |
wait_for_async_insert=0 | 在数据进入缓冲区后立即返回 INSERT。不保证数据一定会被持久化。 | 仅在可以接受数据丢失时使用 |
在 wait_for_async_insert=0 的情况下,错误只会在刷写阶段暴露,且无法追溯到最初的 INSERT。客户端也不会提供背压机制,存在导致服务器过载的风险。
关键设置:
| 设置 | 描述 |
|---|---|
async_insert_max_data_size | 当缓冲区达到此大小(字节)时触发刷写 |
async_insert_busy_timeout_ms | 在达到此超时时间(毫秒)后触发刷写 |
async_insert_max_query_number | 当累计达到此数量的查询后触发刷写 |
会话
仅在需要有状态的服务端功能时才启用会话,例如:
- 临时表(
CREATE TEMPORARY TABLE) - 在多个语句之间保持查询上下文
- 会话级设置(
SET max_threads = 4)
启用会话后,请求会被串行化,以防止同一会话被并发使用。对于不需要会话状态的工作负载,这会引入额外的开销。
使用 ADO.NET(用于兼容 ORM):
支持的数据类型
ClickHouse.Driver 支持所有 ClickHouse 数据类型。下表展示了从数据库读取数据时,ClickHouse 类型与原生 .NET 类型之间的映射关系。
类型映射:从 ClickHouse 读取数据
整数类型
| ClickHouse 类型 | .NET 类型 |
|---|---|
| Int8 | sbyte |
| UInt8 | byte |
| Int16 | short |
| UInt16 | ushort |
| Int32 | int |
| UInt32 | uint |
| Int64 | long |
| UInt64 | ulong |
| Int128 | BigInteger |
| UInt128 | BigInteger |
| Int256 | BigInteger |
| UInt256 | BigInteger |
浮点数类型
| ClickHouse 类型 | .NET 类型 |
|---|---|
| Float32 | float |
| Float64 | double |
| BFloat16 | float |
Decimal 类型
| ClickHouse Type | .NET Type |
|---|---|
| Decimal(P, S) | decimal / ClickHouseDecimal |
| Decimal32(S) | decimal / ClickHouseDecimal |
| Decimal64(S) | decimal / ClickHouseDecimal |
| Decimal128(S) | decimal / ClickHouseDecimal |
| Decimal256(S) | decimal / ClickHouseDecimal |
Decimal 类型转换由 UseCustomDecimals 设置项控制。
布尔类型
| ClickHouse 类型 | .NET 类型 |
|---|---|
| Bool | bool |
字符串类型
| ClickHouse 类型 | .NET 类型 |
|---|---|
| String | string |
| FixedString(N) | string |
默认情况下,String 和 FixedString(N) 列都会以 string 形式返回。请在连接字符串中设置 ReadStringsAsByteArrays=true 以将它们读取为 byte[]。当存储可能不是有效 UTF-8 的二进制数据时,这会非常有用。
日期和时间类型
| ClickHouse Type | .NET Type |
|---|---|
| Date | DateTime |
| Date32 | DateTime |
| DateTime | DateTime |
| DateTime32 | DateTime |
| DateTime64 | DateTime |
| Time | TimeSpan |
| Time64 | TimeSpan |
ClickHouse 在内部将 DateTime 和 DateTime64 值存储为 Unix 时间戳(自 Unix 纪元起的秒或子秒单位)。虽然存储始终为 UTC,但列可以关联一个时区,这会影响值的显示和解释方式。
读取 DateTime 值时,会根据列的时区设置 DateTime.Kind 属性:
| Column Definition | Returned DateTime.Kind | Notes |
|---|---|---|
DateTime('UTC') | Utc | 显式 UTC 时区 |
DateTime('Europe/Amsterdam') | Unspecified | 已应用偏移量 |
DateTime | Unspecified | 按原样保留墙上时钟时间(本地时刻) |
对于非 UTC 时区的列,返回的 DateTime 表示该时区中的墙上时钟时间。使用 ClickHouseDataReader.GetDateTimeOffset() 获取带有该时区正确偏移量的 DateTimeOffset:
对于没有显式时区的列(即使用 DateTime 而不是 DateTime('Europe/Amsterdam')),驱动会返回一个 Kind=Unspecified 的 DateTime。这样可以在不对时区做任何假设的前提下,精确保留与存储值一致的挂钟时间(wall-clock time)。
如果你需要对没有显式时区的列实现具备时区感知的行为,可以:
- 在列定义中使用显式时区:
DateTime('UTC')或DateTime('Europe/Amsterdam') - 在读取之后自行应用时区转换。
JSON 类型
| ClickHouse Type | .NET Type | Notes |
|---|---|---|
| Json | JsonObject | 默认(JsonReadMode=Binary) |
| Json | string | 当 JsonReadMode=String 时 |
JSON 列的返回类型由 JsonReadMode 设置控制:
-
Binary(默认):返回System.Text.Json.Nodes.JsonObject。提供对 JSON 数据的结构化访问,但 JSON 结构中的 ClickHouse 特殊类型(如 IP 地址、UUID、大十进制数值)会被转换为其字符串表示。 -
String:以string形式返回原始 JSON。保留来自 ClickHouse 的精确 JSON 表示,当你需要直接透传而不是解析 JSON,或希望自行处理反序列化时非常有用。
其他类型
| ClickHouse 类型 | .NET 类型 |
|---|---|
| UUID | Guid |
| IPv4 | IPAddress |
| IPv6 | IPAddress |
| Nothing | DBNull |
| Dynamic | 参见注释 |
| Array(T) | T[] |
| Tuple(T1, T2, ...) | Tuple<T1, T2, ...> / LargeTuple |
| Map(K, V) | Dictionary<K, V> |
| Nullable(T) | T? |
| Enum8 | string |
| Enum16 | string |
| LowCardinality(T) | 与 T 相同 |
| SimpleAggregateFunction | 与底层类型相同 |
| Nested(...) | Tuple[] |
| Variant(T1, T2, ...) | 参见注释 |
| QBit(T, dimension) | T[] |
Dynamic 和 Variant 类型会根据每一行中实际的底层类型转换为对应的类型。
几何类型
| ClickHouse Type | .NET Type |
|---|---|
| Point | Tuple<double, double> |
| Ring | Tuple<double, double>[] |
| LineString | Tuple<double, double>[] |
| Polygon | Ring[] |
| MultiLineString | LineString[] |
| MultiPolygon | Polygon[] |
| Geometry | 参见说明 |
Geometry 类型是一个 Variant 类型,可以包含任意几何类型。它会被转换为对应的类型。
类型映射:写入 ClickHouse
在插入数据时,驱动会将 .NET 类型转换为相应的 ClickHouse 类型。下表展示了每种 ClickHouse 列类型可以接受的 .NET 类型。
整数类型
| ClickHouse 类型 | 可接受的 .NET 类型 | 备注 |
|---|---|---|
| Int8 | sbyte 以及任何与 Convert.ToSByte() 兼容的类型 | |
| UInt8 | byte 以及任何与 Convert.ToByte() 兼容的类型 | |
| Int16 | short 以及任何与 Convert.ToInt16() 兼容的类型 | |
| UInt16 | ushort 以及任何与 Convert.ToUInt16() 兼容的类型 | |
| Int32 | int 以及任何与 Convert.ToInt32() 兼容的类型 | |
| UInt32 | uint 以及任何与 Convert.ToUInt32() 兼容的类型 | |
| Int64 | long 以及任何与 Convert.ToInt64() 兼容的类型 | |
| UInt64 | ulong 以及任何与 Convert.ToUInt64() 兼容的类型 | |
| Int128 | BigInteger、decimal、double、float、int、uint、long、ulong,以及任何与 Convert.ToInt64() 兼容的类型 | |
| UInt128 | BigInteger、decimal、double、float、int、uint、long、ulong,以及任何与 Convert.ToInt64() 兼容的类型 | |
| Int256 | BigInteger、decimal、double、float、int、uint、long、ulong,以及任何与 Convert.ToInt64() 兼容的类型 | |
| UInt256 | BigInteger、decimal、double、float、int、uint、long、ulong,以及任何与 Convert.ToInt64() 兼容的类型 |
浮点类型
| ClickHouse Type | 可接受的 .NET 类型 | 说明 |
|---|---|---|
| Float32 | float,任何与 Convert.ToSingle() 兼容的类型 | |
| Float64 | double,任何与 Convert.ToDouble() 兼容的类型 | |
| BFloat16 | float,任何与 Convert.ToSingle() 兼容的类型 | 截断为 16 位 bfloat16 格式 |
布尔类型
| ClickHouse 类型 | 可接受的 .NET 类型 | 备注 |
|---|---|---|
| Bool | bool |
字符串类型
| ClickHouse 类型 | 可接受的 .NET 类型 | 说明 |
|---|---|---|
| String | string,byte[],ReadOnlyMemory<byte>,Stream | 二进制类型将被直接写入;流可以是可查找(seekable)或不可查找(non-seekable) |
| FixedString(N) | string,byte[],ReadOnlyMemory<byte>,Stream | 字符串以 UTF-8 编码并进行填充;二进制类型的长度必须正好为 N 字节 |
日期和时间类型
| ClickHouse Type | 可接受的 .NET 类型 | 说明 |
|---|---|---|
| Date | DateTime, DateTimeOffset, DateOnly, NodaTime 类型 | 转换为自 Unix 纪元起的天数,类型为 UInt16 |
| Date32 | DateTime, DateTimeOffset, DateOnly, NodaTime 类型 | 转换为自 Unix 纪元起的天数,类型为 Int32 |
| DateTime | DateTime, DateTimeOffset, DateOnly, NodaTime 类型 | 详细行为见下文 |
| DateTime32 | DateTime, DateTimeOffset, DateOnly, NodaTime 类型 | 与 DateTime 相同 |
| DateTime64 | DateTime, DateTimeOffset, DateOnly, NodaTime 类型 | 精度由 Scale 参数决定 |
| Time | TimeSpan, int | 限制在 ±999:59:59 范围内;int 按秒解释 |
| Time64 | TimeSpan, decimal, double, float, int, long, string | string 按 [-]HHH:MM:SS[.fraction] 解析;限制在 ±999:59:59.999999999 范围内 |
在写入值时,驱动会遵循 DateTime.Kind:
| DateTime.Kind | HTTP 参数 | 批量写入 |
|---|---|---|
| Utc | 精确保留同一时间点 | 精确保留同一时间点 |
| Local | 精确保留同一时间点 | 精确保留同一时间点 |
| Unspecified | 按参数类型所在时区的本地墙上时间处理(默认使用 UTC 时区) | 按列所在时区的本地墙上时间处理 |
DateTimeOffset 值始终精确保留时间点。
示例:UTC DateTime(时间点保持不变)
示例:未指定的 DateTime(墙上时钟时间)
建议: 为获得最简单且最可预测的行为,请在所有与 DateTime 相关的操作中使用 DateTimeKind.Utc 或 DateTimeOffset。这样可以确保无论服务器时区、客户端时区还是列时区如何,代码都能保持一致的运行方式。
HTTP 参数与批量复制
在写入 Unspecified 的 DateTime 值时,通过 HTTP 参数绑定和通过批量复制之间存在一个重要区别:
Bulk Copy 知道目标列的时区,并会在该时区中正确解释 Unspecified 值。
HTTP Parameters 无法自动获知列的时区。你必须在 SQL 类型提示中显式指定该时区:
DateTime.Kind | 目标列 | HTTP 参数(带时区提示) | HTTP 参数(无时区提示) | 批量复制 |
|---|---|---|---|---|
Utc | UTC | 时间点保持不变 | 时间点保持不变 | 时间点保持不变 |
Utc | Europe/Amsterdam | 时间点保持不变 | 时间点保持不变 | 时间点保持不变 |
Local | 任意 | 时间点保持不变 | 时间点保持不变 | 时间点保持不变 |
Unspecified | UTC | 视为 UTC | 视为 UTC | 视为 UTC |
Unspecified | Europe/Amsterdam | 视为阿姆斯特丹时间 | 视为 UTC | 视为阿姆斯特丹时间 |
Decimal 类型
| ClickHouse 类型 | 可接受的 .NET 类型 | 说明 |
|---|---|---|
| Decimal(P,S) | decimal, ClickHouseDecimal, 任意与 Convert.ToDecimal() 兼容的类型 | 当超出精度时抛出 OverflowException |
| Decimal32 | decimal, ClickHouseDecimal, 任意与 Convert.ToDecimal() 兼容的类型 | 最大精度为 9 |
| Decimal64 | decimal, ClickHouseDecimal, 任意与 Convert.ToDecimal() 兼容的类型 | 最大精度为 18 |
| Decimal128 | decimal, ClickHouseDecimal, 任意与 Convert.ToDecimal() 兼容的类型 | 最大精度为 38 |
| Decimal256 | decimal, ClickHouseDecimal, 任意与 Convert.ToDecimal() 兼容的类型 | 最大精度为 76 |
JSON 类型
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| Json | string, JsonObject, JsonNode, any object | 行为取决于 JsonWriteMode SETTING |
写入 JSON 时的行为由 JsonWriteMode 设置控制:
| Input Type | JsonWriteMode.String (default) | JsonWriteMode.Binary |
|---|---|---|
string | 原样传递 | 抛出 ArgumentException |
JsonObject | 通过 ToJsonString() 序列化 | 抛出 ArgumentException |
JsonNode | 通过 ToJsonString() 序列化 | 抛出 ArgumentException |
| Registered POCO | 通过 JsonSerializer.Serialize() 序列化 | 使用带类型提示的二进制编码,支持自定义路径特性 |
| Unregistered POCO / Anonymous object | 通过 JsonSerializer.Serialize() 序列化 | 抛出 ClickHouseJsonSerializationException |
-
String(默认):接受string、JsonObject、JsonNode或任意对象。所有输入都会通过System.Text.Json.JsonSerializer序列化,并以 JSON 字符串形式发送到服务端进行解析。该模式最为灵活,无需进行类型注册。 -
Binary:仅接受已注册的 POCO 类型。数据会在客户端转换为 ClickHouse 的二进制 JSON 格式,并具备完整的类型提示支持。使用前需要调用connection.RegisterJsonSerializationType<T>()。在该模式下写入string或JsonNode值会抛出ArgumentException。
带类型的 JSON 列
当一个 JSON 列带有类型提示时(例如 JSON(id UInt64, price Decimal128(2))),驱动程序会利用这些提示,在序列化值时保留完整的类型信息。这样可以保持 UInt64、Decimal、UUID 和 DateTime64 等类型的精度,否则在序列化为通用 JSON 时会丢失精度。
POCO 序列化
根据 JsonWriteMode 的不同,POCO 可以通过两种方式写入 JSON 列:
字符串模式(默认):POCO 使用 System.Text.Json.JsonSerializer 进行序列化。无需进行类型注册。这是最简单的方式,并且适用于匿名对象。
二进制模式:POCO 使用驱动的二进制 JSON 格式进行序列化,并提供完整的类型提示支持。在使用前,必须通过 connection.RegisterJsonSerializationType<T>() 注册类型。此模式通过特性(attribute)支持自定义 JSON 路径映射:
-
[ClickHouseJsonPath("path")]:将属性映射到自定义 JSON 路径。对于嵌套结构,或当属性名与期望的 JSON 键不同的情况非常有用。仅在二进制模式下有效。 -
[ClickHouseJsonIgnore]:在序列化中排除某个属性。仅在二进制模式下有效。
属性名称与列类型提示的匹配是区分大小写的。属性 UserId 只会匹配定义为 UserId 的提示,而不会匹配 userid。这与 ClickHouse 的行为一致,它允许诸如 userName 和 UserName 这样的路径作为不同字段并存。
限制(仅 Binary 模式):
- 在序列化之前,必须在连接上使用
connection.RegisterJsonSerializationType<T>()先注册 POCO 类型。尝试序列化未注册的类型会抛出ClickHouseJsonSerializationException。 - 字典和数组/列表属性在列定义中需要提供类型提示,才能被正确序列化。如果没有类型提示,请改用 String 模式。
- 只有当路径在列定义中具有
Nullable(T)类型提示时,POCO 属性中的空值才会被写入。ClickHouse 不允许在动态 JSON 路径中使用Nullable类型,因此未提供类型提示的空属性会被跳过。 - 在 String 模式下会忽略
ClickHouseJsonPath和ClickHouseJsonIgnore特性(它们仅在 Binary 模式下生效)。
其他类型
| ClickHouse 类型 | 可接受的 .NET 类型 | 说明 |
|---|---|---|
| UUID | Guid, string | 字符串将被解析为 Guid |
| IPv4 | IPAddress, string | 必须为 IPv4;字符串通过 IPAddress.Parse() 解析 |
| IPv6 | IPAddress, string | 必须为 IPv6;字符串通过 IPAddress.Parse() 解析 |
| Nothing | 任意类型 | 不写入任何内容(no-op) |
| Dynamic | — | 不支持(抛出 NotImplementedException) |
| Array(T) | IList, null | 将 null 写为空数组 |
| Tuple(T1, T2, ...) | ITuple, IList | 元素数量必须与元组的元数一致 |
| Map(K, V) | IDictionary | |
| Nullable(T) | null, DBNull,或 T 可接受的类型 | 在值前写入一个 null 标志字节 |
| Enum8 | string, sbyte, 数值类型 | 字符串将在枚举字典中查找对应值 |
| Enum16 | string, short, 数值类型 | 字符串将在枚举字典中查找对应值 |
| LowCardinality(T) | T 可接受的类型 | 委托给底层类型 |
| SimpleAggregateFunction | 底层类型可接受的类型 | 委托给底层类型 |
| Nested(...) | 元组组成的 IList | 元素数量必须与字段数量一致 |
| Variant(T1, T2, ...) | 匹配 T1、T2 等之一的值 | 如果没有类型匹配则抛出 ArgumentException |
| QBit(T, dim) | IList | 委托给 Array;维度仅作为元数据存在 |
几何类型
| ClickHouse Type | 接受的 .NET 类型 | 说明 |
|---|---|---|
| Point | System.Drawing.Point、ITuple、IList(含 2 个元素) | |
| Ring | Point 的 IList | |
| LineString | Point 的 IList | |
| Polygon | Ring 的 IList | |
| MultiLineString | LineString 的 IList | |
| MultiPolygon | Polygon 的 IList | |
| Geometry | 以上任意几何类型 | 包含所有几何类型的变体 |
不支持写入
| ClickHouse 类型 | 说明 |
|---|---|
| Dynamic | 会抛出 NotImplementedException |
| AggregateFunction | 会抛出 AggregateFunctionException |
嵌套类型处理
ClickHouse 嵌套类型(Nested(...))可以按照数组语义进行读写。
日志和诊断
ClickHouse .NET 客户端与 Microsoft.Extensions.Logging 抽象层集成,提供轻量级、可选启用的日志记录功能。启用后,驱动程序会针对连接生命周期事件、命令执行、传输操作以及批量插入操作输出结构化消息。日志记录完全是可选的——未配置记录器的应用程序将继续正常运行,并且不会引入任何额外开销。
快速开始
使用 appsettings.json
可以使用标准的 .NET 配置来配置日志级别:
使用内存配置
你也可以在代码中按类别配置日志的详细程度:
分类与发射源
驱动程序使用专用日志分类,便于按组件精细调整日志级别:
| 分类 | 源 | 要点 |
|---|---|---|
ClickHouse.Driver.Connection | ClickHouseConnection | 连接生命周期、HTTP 客户端工厂的选择、连接打开/关闭、会话管理。 |
ClickHouse.Driver.Command | ClickHouseCommand | 查询执行开始/完成、计时、查询 ID、服务器统计信息和错误详情。 |
ClickHouse.Driver.Transport | ClickHouseConnection | 底层 HTTP 流式请求、压缩标志、响应状态码以及传输失败情况。 |
ClickHouse.Driver.Client | ClickHouseClient | 二进制插入、查询以及其他操作。 |
ClickHouse.Driver.NetTrace | TraceHelper | 网络跟踪,仅在启用调试模式时生效。 |
示例:排查连接问题
这将记录:
- HTTP 客户端工厂的选择(默认连接池 vs 单连接)
- HTTP 处理程序配置(SocketsHttpHandler 或 HttpClientHandler)
- 连接池设置(MaxConnectionsPerServer、PooledConnectionLifetime 等)
- 超时设置(ConnectTimeout、Expect100ContinueTimeout 等)
- SSL/TLS 配置
- 连接打开/关闭事件
- 会话 ID 跟踪
调试模式:网络跟踪与诊断
为帮助诊断网络问题,驱动程序库提供了一个辅助工具,可启用对 .NET 网络内部机制的底层跟踪。要启用它,必须传入一个 LoggerFactory,并将日志级别设置为 Trace,同时将 EnableDebugMode 设置为 true(或者通过 ClickHouse.Driver.Diagnostic.TraceHelper 类手动启用)。日志事件将记录到 ClickHouse.Driver.NetTrace 类别中。警告:这会生成极其冗长的日志,并影响性能。不建议在生产环境中启用调试模式。
OpenTelemetry
该驱动通过 .NET System.Diagnostics.Activity API 提供对 OpenTelemetry 分布式追踪的内置支持。启用后,驱动会为数据库操作生成 spans,这些 spans 可以导出到 Jaeger 等可观测性后端系统,或者通过 OpenTelemetry Collector 导出到 ClickHouse 本身。
启用追踪
在 ASP.NET Core 应用程序中,将 ClickHouse 驱动程序的 ActivitySource 添加到 OpenTelemetry 配置中:
用于控制台应用程序、测试或手动设置:
Span 属性
每个 span 都包含标准的 OpenTelemetry 数据库属性,以及可用于调试的 ClickHouse 特有查询统计信息。
| Attribute | Description |
|---|---|
db.system | 始终为 "clickhouse" |
db.name | 数据库名称 |
db.user | 用户名 |
db.statement | SQL 查询(如果已启用) |
db.clickhouse.read_rows | 查询读取的行数 |
db.clickhouse.read_bytes | 查询读取的字节数 |
db.clickhouse.written_rows | 查询写入的行数 |
db.clickhouse.written_bytes | 查询写入的字节数 |
db.clickhouse.elapsed_ns | 服务器端执行时间(纳秒) |
配置选项
通过 ClickHouseDiagnosticsOptions 控制跟踪行为:
启用 IncludeSqlInActivityTags 可能会在跟踪数据中暴露敏感数据。在生产环境中请谨慎使用。
TLS 配置
通过 HTTPS 连接到 ClickHouse 时,可以使用多种方式配置 TLS/SSL 行为。
自定义证书验证
在生产环境中如需自定义证书验证逻辑,请提供一个自定义的 HttpClient 实例,并配置 ServerCertificateCustomValidationCallback 处理程序:
在提供自定义 HttpClient 时需要注意的重要事项
- 自动解压缩:如果未禁用压缩功能(默认启用压缩),必须启用
AutomaticDecompression。 - 空闲超时:将
PooledConnectionIdleTimeout设置为小于服务器的keep_alive_timeout(ClickHouse Cloud 默认为 10 秒),以避免由于半开连接导致的连接错误。
ORM 支持
ORM 依赖 ADO.NET API(ClickHouseConnection)。为正确管理连接的生命周期,应从 ClickHouseDataSource 创建连接:
Dapper
ClickHouse.Driver 可以与 Dapper 一起使用,但不支持匿名对象。
可运行示例:
不支持:
Linq2db
此驱动程序兼容 linq2db,这是一个用于 .NET 的轻量级 ORM 和 LINQ 提供程序。有关详细文档,请参阅该项目主页。
使用示例:
使用 ClickHouse 提供程序创建一个 DataConnection 实例:
可以使用特性或 fluent 配置来定义表映射。如果类名和属性名与表名和列名完全一致,则无需额外配置:
查询:
批量复制:
使用 BulkCopyAsync 以高效地执行批量插入。
Entity framework core
当前尚不支持 Entity Framework Core。
限制
AggregateFunction 列
类型为 AggregateFunction(...) 的列不能直接进行查询或插入操作。
要进行插入:
选择: