跳转到主要内容
用于连接 ClickHouse 的官方 C# 客户端。 该客户端的源代码可在 GitHub 仓库 中获取。 最初由 Oleg V. Kozlyuk 开发。 该库提供两个主要 API:
  • ClickHouseClient (推荐) :一个高级、线程安全的客户端,适合以单例方式使用。为查询和批量插入提供简洁的异步 API。最适合大多数应用程序。
  • ADO.NET (ClickHouseDataSourceClickHouseConnectionClickHouseCommand) :标准的 .NET 数据库抽象。ORM 集成 (Dapper、Linq2db) 以及需要 ADO.NET 兼容性时必须使用。ClickHouseBulkCopy 是一个辅助类,用于通过 ADO.NET 连接高效插入数据。ClickHouseBulkCopy 已弃用,并将在未来的版本中移除;请改用 ClickHouseClient.InsertBinaryAsync
这两种 API 共享同一个底层 HTTP 连接池,并且可以在同一个应用程序中同时使用。

迁移指南

  1. .csproj 文件中的包名更新为 ClickHouse.Driver,并使用 NuGet 上的最新版本
  2. 将代码库中所有对 ClickHouse.Client 的引用替换为 ClickHouse.Driver

支持的 .NET 版本

ClickHouse.Driver 支持以下 .NET 版本:
  • .NET 6.0
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

安装

通过 NuGet 安装该包:
或者使用 NuGet 包管理器:

快速入门

配置

有两种方式可用于配置与 ClickHouse 的连接:
  • **连接字符串:**由分号分隔的键/值对,用于指定主机、身份验证凭据和其他连接选项。
  • **ClickHouseClientSettings object:**强类型配置对象,可从配置文件中加载,也可在代码中设置。
下面列出了所有设置、它们的默认值及其作用。

连接设置

数据格式与序列化

会话管理

UseSession 标志会启用服务器会话持久化,从而可以使用 SET 语句和临时表。会话在 60 秒无活动后会被重置 (默认超时) 。可通过 ClickHouse 语句或服务器配置中的会话设置来延长会话生命周期。ClickHouseConnection 类通常支持并行操作 (多个线程可并发运行查询) 。但启用 UseSession 标志后,任意时刻每个连接只允许有一个活动查询 (这是服务器端限制) 。

安全

HTTP 客户端配置

日志与调试

自定义设置与角色

使用连接字符串设置自定义设置时,请使用 set_ 前缀,例如 set_max_threads=4。使用 ClickHouseClientSettings 对象时,不要使用 set_ 前缀。有关可用设置的完整列表,请参见此处

连接字符串示例

基本连接

使用自定义 ClickHouse 设置


QueryOptions

QueryOptions 允许你按查询覆盖客户端级别的设置。所有属性均为可选,只有在指定时才会覆盖客户端默认值。 示例:

InsertOptions

InsertOptionsQueryOptions 的基础上增加了通过 InsertBinaryAsync 执行批量插入操作所需的特定设置。 QueryOptions 的所有属性也可用于 InsertOptions 示例:

跳过 schema 探测查询

默认情况下,InsertBinaryAsync 会在每次 insert 之前发送一个 SELECT ... WHERE 1=0 查询,以探测列类型。对于高吞吐量场景,你可以通过以下两种方式消除这部分开销: 选项 1:显式提供列类型 当你在编译时就已知表的 schema 时,可通过 ColumnTypes 直接传入。这样就完全不会发送 schema 查询:
选项 2:缓存 schema 当你反复向同一个表插入数据时,可设置 UseSchemaCache = true,这样只需查询一次 schema,后续在同一个 ClickHouseClient 实例上插入时即可复用:
  • ColumnTypes 的优先级高于 UseSchemaCache。如果两者都已设置,则使用显式指定的类型。
  • schema 缓存无法检测 ALTER TABLE 带来的变更。如果你修改了表的 schema,请创建新的 ClickHouseClient,或避免对该表使用 UseSchemaCache
  • 缓存的作用域仅限于 ClickHouseClient 实例,并以 (database,table) 为键。同一张表的不同列子集会共享同一个缓存的 schema。

ClickHouseClient

ClickHouseClient 是与 ClickHouse 交互时推荐使用的 API。它是线程安全的,采用单例模式设计,并在内部管理 HTTP 连接池。

创建客户端

使用连接字符串或 ClickHouseClientSettings 对象创建 ClickHouseClient。可用选项请参阅配置部分。 你的 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 自动拉取表结构。提供的值必须与目标列的类型匹配。若要跳过此查询,请使用 InsertOptions.ColumnTypesInsertOptions.UseSchemaCache
  • MaxDegreeOfParallelism > 1 时,批次会并行上传。会话与并行插入不兼容;请禁用会话,或将 MaxDegreeOfParallelism 设为 1
  • 如果你希望服务器为未提供的列应用 DEFAULT 值,请在 InsertOptions.Format 中使用 RowBinaryFormat.RowBinaryWithDefaults

POCO 插入

无需构造 object[] 数组,可直接插入强类型的 POCO 对象。只需注册一次该类型,然后传入 IEnumerable<T>
默认情况下,所有公开可读属性都会通过严格区分大小写的名称匹配映射到列。你可以使用特性来自定义映射:
所有映射属性都显式指定了 Type 时,会完全跳过 schema 探测查询。只有部分属性显式指定类型时,驱动程序会回退为对完整列集执行 schema 探测查询。 InsertBinaryAsync<T> 支持与 object[] 重载相同的 InsertOptions (批处理、并行度、schema 缓存) 。
object[] 重载不同,InsertBinaryAsync<T> 不接受显式列列表。列由已注册类型的映射属性决定。要控制插入哪些列,可使用 [ClickHouseNotMapped] 排除属性,或使用 [ClickHouseColumn(Name = "...")] 为属性重命名。如果在 InsertOptions 中设置了 ColumnTypes,它们会覆盖 POCO 特性。

schema 演进

即使在类型注册完成后向目标表新增列,POCO 插入也能无缝运行。由于 驱动 只会插入由 POCO 映射的列,任何带有 DEFAULT (或其他默认表达式) 的新列都会由 server 自动补齐。无需修改代码,也无需重新注册。

读取数据

使用 ExecuteReaderAsync 执行 SELECT 查询。返回的 ClickHouseDataReader 可通过 GetInt64()GetString()GetFieldValue<T>() 等方法,以强类型方式访问结果列。 调用 Read() 以移动到下一行。没有更多行时,它会返回 false。可以按索引 (从 0 开始) 或列名访问列。

SQL 参数

在 ClickHouse 中,SQL 查询中的查询参数标准格式为 {parameter_name:DataType} 示例:
SQL ‘绑定’参数通过 HTTP URI 查询参数传递,因此如果使用过多,可能会导致出现 “URL 过长” 异常。为避免这一限制,在批量插入数据时请使用 InsertBinaryAsync

查询 ID

每个查询都会被分配一个唯一的 query_id,可用于从 system.query_log 表中查询数据,或取消长时间运行的查询。你可以通过 QueryOptions 指定自定义的查询 ID:
如果要指定自定义 QueryId,请确保每次调用使用的值都是唯一的。随机生成的 GUID 是不错的选择。

自定义参数类型映射

使用 @ 风格的参数时 (例如 WHERE id = @id) ,驱动程序会根据 .NET 值类型自动推断 ClickHouse 类型。例如,int 会映射为 Int32DateTime 会映射为 DateTime 如需覆盖这些默认映射,请在 ClickHouseClientSettings 上设置 ParameterTypeResolver。例如,如果你希望所有 DateTime 参数都使用具有毫秒精度的 DateTime64(3),或者希望所有 Decimal 参数都使用特定的标度,而不必为每个参数单独设置 ClickHouseType,这会很有用。 使用 DictionaryParameterTypeResolver 进行简单的类型映射:
用于高级场景的自定义 IParameterTypeResolver 对于按值或按名称进行解析的场景,请直接实现 IParameterTypeResolver 接口。返回 null 以回退到默认推断:
你也可以通过 QueryOptions.ParameterTypeResolver 为单个查询设置解析器。设置后,它会优先于客户端级别的解析器。 类型解析优先级: 解析器只是这一优先级事件链中的一环。按优先级从高到低依次为:
  1. 在参数上显式设置的 ClickHouseType
  2. 查询中通过 {name:Type} 语法指定的 SQL 类型提示
  3. IParameterTypeResolver (来自 QueryOptions.ParameterTypeResolver,若未设置则回退到 ClickHouseClientSettings.ParameterTypeResolver)
  4. 内置类型推断 (TypeConverter.ToClickHouseType)
该解析器也适用于 ADO.NET 的 ClickHouseConnection 路径——由客户端创建的连接会继承这些设置。

原始流式传输

使用 ExecuteRawResultAsync 可按特定 格式 直接流式传输查询结果,绕过数据读取器。这对于将数据导出到文件或传输到其他系统特别有用:
常见格式:JSONEachRowCSVTSVParquetNative。所有选项请参阅格式文档

原始流插入

使用 InsertRawStreamAsync 可直接从文件或内存流插入数据,支持 CSV、JSON、Parquet 等格式,以及任何ClickHouse 支持的格式 从 CSV 文件插入:
有关控制数据摄取行为的选项,请参阅格式设置文档

更多示例

如需更多实用用法示例,请参阅 GitHub 仓库中的 examples 目录

ADO.NET

该库通过 ClickHouseConnectionClickHouseCommandClickHouseDataReader 提供完整的 ADO.NET 支持。ORM 集成 (Dapper、Linq2db) 以及需要标准 .NET 数据库抽象时,都必须使用此 API。

使用 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.HttpClientHttpClient 会为每个端点维护一个连接池。因此:
  • 数据库会话会通过连接池管理的 HTTP 连接进行多路复用。
  • HTTP 连接会由连接池自动复用和回收。
  • 即使 ClickHouseClientClickHouseConnection 对象已释放,连接仍可能继续保持活动状态。
推荐做法:
使用自定义 HttpClientHttpClientFactory 时,请确保将 PooledConnectionIdleTimeout 设置为小于服务器 keep_alive_timeout 的值,以避免因连接半关闭而导致错误。Cloud 部署的默认 keep_alive_timeout 为 10 秒。
避免在未共享 HttpClient 的情况下创建多个 ClickHouseClient 或独立的 ClickHouseConnection 实例。每个实例都会创建自己的连接池。

DateTime 处理

  1. 尽可能使用 UTC。 将时间戳存储为 DateTime('UTC') 列,并在代码中使用 DateTimeKind.Utc。这样可以避免时区歧义。
  2. 使用 DateTimeOffset 进行明确的时区处理。 它始终表示某个确定的时间点,并包含偏移信息。
  3. 在 SQL 类型提示中指定时区。 当参数中使用 Unspecified 的 DateTime 值,且目标列不是 UTC 时,请在 SQL 中包含时区信息:

异步插入

异步插入 将批处理的责任从客户端转移到服务器。服务器不再要求客户端进行批处理,而是缓冲传入的数据,并根据可配置的阈值将其刷写到存储中。这对于高并发场景非常有用,例如在可观测性工作负载中,大量 agent 会发送小型载荷。 可通过 CustomSettings 或连接字符串启用异步插入:
两种模式 (由 wait_for_async_insert 控制) :
使用 wait_for_async_insert=0 时,错误只会在刷新期间暴露出来,且无法追溯到原始插入。客户端也不会提供背压,存在服务器过载的风险。
关键设置:

会话

仅在需要有状态的服务器端功能时才启用会话,例如:
  • 临时表 (CREATE TEMPORARY TABLE)
  • 在多条语句之间保持查询上下文
  • 会话级设置 (SET max_threads = 4)
启用会话后,请求会按顺序串行处理,以防止同一会话被并发使用。对于不需要会话状态的 workloads,这会带来额外开销。
使用 ADO.NET (兼容 ORM) :

支持的数据类型

ClickHouse.Driver 支持所有 ClickHouse 数据类型。下表展示了从数据库读取数据时,ClickHouse 类型与原生 .NET 类型之间的映射。

类型映射:从 ClickHouse 读取

整型


浮点类型


Decimal 类型

Decimal 类型的转换由 UseCustomDecimals 设置控制。

布尔类型


String 类型

默认情况下,StringFixedString(N) 列都会作为 string 返回。要将它们改为读取为 byte[],请在连接字符串中设置 ReadStringsAsByteArrays=true。这在存储可能不是有效 UTF-8 的二进制数据时非常有用。

日期和时间类型

ClickHouse 在内部将 DateTimeDateTime64 值存储为 Unix 时间戳 (即自纪元以来的秒或亚秒单位) 。虽然存储始终采用 UTC,但列可以关联一个时区,这会影响值的显示和解析方式。 读取 DateTime 值时,DateTime.Kind 属性会根据列的时区进行设置: 对于非 UTC 列,返回的 DateTime 表示该时区中的挂钟时间。使用 ClickHouseDataReader.GetDateTimeOffset() 可获取带有该时区正确偏移量的 DateTimeOffset
对于没有显式指定时区的列 (即 DateTime,而不是 DateTime('Europe/Amsterdam')) ,驱动程序 会返回一个 Kind=UnspecifiedDateTime。这样可以原样保留存储的挂钟时间,而不对时区作任何假定。 如果你需要让没有显式时区的列具备时区感知行为,可以:
  1. 在列定义中显式指定时区:DateTime('UTC')DateTime('Europe/Amsterdam')
  2. 读取后自行应用时区。

JSON 类型

JSON 列的返回类型由 JsonReadMode 设置控制:
  • Binary (默认) :返回 System.Text.Json.Nodes.JsonObject。可对 JSON 数据进行结构化访问,但专用的 ClickHouse 类型 (如 IP 地址、UUID、较大精度的 Decimal) 会在 JSON 结构中转换为字符串表示形式。
  • String:以 string 形式返回原始 JSON。保留 ClickHouse 中 JSON 的精确表示形式,这在你需要不经解析直接传递 JSON,或想自行处理反序列化时非常有用。

其他类型

Dynamic 和 Variant 类型会转换为每一行实际底层类型对应的类型。

几何类型

Geometry 类型是一种 Variant 类型,可容纳任意几何类型。它会被转换为对应的类型。

类型映射:写入 ClickHouse

插入数据时,驱动程序会将 .NET 类型转换为相应的 ClickHouse 类型。下表列出了每种 ClickHouse 列类型可接受的 .NET 类型。

整数类型


浮点类型


布尔类型


String 类型


日期和时间类型

驱动在写入值时会遵循 DateTime.Kind DateTimeOffset 值始终保留精确时刻。 示例:UTC DateTime (保留精确时刻)
示例:未指定 DateTime (挂钟时间)
**建议:**为获得最简单且最可预测的行为,所有 DateTime 操作都使用 DateTimeKind.UtcDateTimeOffset。这样可以确保你的代码始终保持一致,不受服务器时区、客户端时区或列时区的影响。

HTTP 参数与批量复制

在写入 Unspecified DateTime 值时,HTTP 参数绑定和批量复制之间有一个重要区别: 批量复制 知道目标列的时区,因此会按该时区正确解释 Unspecified 值。 HTTP 参数 不会自动获知列的时区。你必须在 SQL 类型提示中显式指定它:

Decimal 类型


JSON 类型

写入 JSON 时的行为由 JsonWriteMode 设置控制:
  • String (默认) :接受 stringJsonObjectJsonNode 或任意对象。所有输入都会通过 System.Text.Json.JsonSerializer 序列化,并作为 JSON 字符串发送到服务端解析。这是最灵活的模式,无需注册类型即可使用。
  • Binary:仅接受已注册的 POCO 类型。数据会在客户端转换为 ClickHouse 的二进制 JSON 格式,并完整支持类型提示。使用前需要调用 connection.RegisterJsonSerializationType<T>()。在此模式下写入 stringJsonNode 值会抛出 ArgumentException
带类型提示的 JSON 列
当 JSON 列带有类型提示 (例如 JSON(id UInt64, price Decimal128(2))) 时,驱动程序会利用这些提示对值进行序列化,从而完整保留类型信息。这样可以保留 UInt64DecimalUUIDDateTime64 等类型的精度,否则它们在按通用 JSON 序列化时可能会损失精度。
POCO 序列化
根据 JsonWriteMode,可通过两种方式将 POCO 写入 JSON 列: String 模式 (默认) :POCO 通过 System.Text.Json.JsonSerializer 进行序列化。无需注册类型。这是最简单的方法,也适用于匿名对象。 Binary 模式:POCO 使用驱动的二进制 JSON 格式进行序列化,并完整支持 类型提示。使用前必须通过 connection.RegisterJsonSerializationType<T>() 注册类型。此模式还支持通过特性自定义 path 映射:
  • [ClickHouseJsonPath("path")]:将属性映射到自定义 JSON path。适用于嵌套结构,或属性名与所需的 JSON 键不一致时。仅在 Binary 模式下有效。
  • [ClickHouseJsonIgnore]:序列化时排除此属性。仅在 Binary 模式下有效。
属性名称与列类型提示的匹配是区分大小写的。属性 UserId 只会匹配定义为 UserId 的提示,不会匹配 userid。这与 ClickHouse 的行为一致:它允许 userNameUserName 作为两个不同的字段并存。 限制 (仅 Binary 模式) :
  • 在序列化之前,必须通过 connection.RegisterJsonSerializationType<T>() 在 connection 上注册 POCO 类型。尝试序列化未注册的类型会抛出 ClickHouseJsonSerializationException
  • 字典以及数组/列表属性需要在列定义中提供类型提示,才能正确序列化。没有提示时,请改用 String 模式。
  • 只有当该 path 在列定义中具有 Nullable(T) 类型提示时,POCO 属性中的 NULL 值才会被写入。ClickHouse 不允许在动态 JSON path 中使用 Nullable 类型,因此未提供提示的 null 属性会被跳过。
  • 在 String 模式下,ClickHouseJsonPathClickHouseJsonIgnore 特性会被忽略 (它们仅在 Binary 模式下生效) 。

其他类型


几何类型


不支持写入的类型


嵌套类型处理

ClickHouse 嵌套类型 (Nested(...)) 可按数组语义进行读写。

日志与诊断

ClickHouse .NET 客户端集成了 Microsoft.Extensions.Logging 抽象,提供轻量、按需启用的日志功能。启用后,驱动程序会针对连接生命周期事件、命令执行、传输操作以及批量插入操作输出结构化消息。日志功能完全是可选的——未配置日志记录器的应用程序仍可继续运行,且不会带来额外开销。

快速入门

使用 appsettings.json

你可以通过标准的 .NET 配置来设置日志级别:

使用内存中的配置

你也可以在代码中按类别配置日志详细级别:

类别和发出方

该驱动使用专门的类别,以便你可以按组件精细调整日志级别:

示例:排查连接问题

这将记录:
  • HTTP 客户端工厂的选择 (默认连接池或单个连接)
  • HTTP handler 配置 (SocketsHttpHandler 或 HttpClientHandler)
  • 连接池设置 (MaxConnectionsPerServer、PooledConnectionLifetime 等)
  • 超时设置 (ConnectTimeout、Expect100ContinueTimeout 等)
  • SSL/TLS 配置
  • 连接打开/关闭事件
  • 会话 ID 跟踪

调试模式:网络跟踪与诊断

为帮助诊断网络问题,驱动库提供了一个辅助工具,可启用对 .NET 网络内部机制的底层跟踪。要启用该功能,必须传入一个级别设为 Trace 的 LoggerFactory,并将 EnableDebugMode 设置为 true (或者通过 ClickHouse.Driver.Diagnostic.TraceHelper 类手动启用) 。事件会记录到 ClickHouse.Driver.NetTrace 类别中。警告:这会生成极其详细的日志,并影响性能。不建议在生产环境中启用调试模式。

OpenTelemetry

该驱动程序内置了对通过 .NET System.Diagnostics.Activity API 实现的 OpenTelemetry 分布式链路追踪的支持。启用后,驱动程序会为数据库操作生成 span,并可将其导出到 Jaeger 或 ClickHouse 自身等可观测性后端 (通过 OpenTelemetry Collector) 。

启用链路追踪

在 ASP.NET Core 应用中,将 ClickHouse 驱动的 ActivitySource 添加到 OpenTelemetry 配置中:
对于控制台应用程序、测试或手动配置:

Span 属性

每个 span 都包含标准的 OpenTelemetry 数据库属性,以及可用于调试的 ClickHouse 特有查询统计信息。

配置选项

通过 ClickHouseDiagnosticsOptions 控制链路追踪行为:
启用 IncludeSqlInActivityTags 可能会在链路追踪中泄露敏感数据。在 production 环境中使用时请务必谨慎。

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 配合使用。该驱动程序会自动将 Dapper 的 @parameter 语法转换为 ClickHouse 的原生 {parameter:Type} 语法,并根据 .NET 值推断类型。 使用 ClickHouseDataSource 以正确管理连接的生命周期:

参数传递方式

支持 Dapper 的所有标准参数传递方式: 匿名对象:
POCO 类:
字典:
DynamicParameters (来自字典或匿名对象) :

将查询结果映射到 POCO

Dapper 会按名称将列映射到属性 (不区分大小写) :

ClickHouse 原生参数语法

当需要显式控制类型时,可直接在 SQL 中使用 ClickHouse 的 {param:Type} 语法,并通过 Dictionary<string, object> 提供参数值。不要对同一个参数同时使用 @param 语法和 {param:Type} 语法。

WHERE IN

Dapper 原生支持 IN 展开:
Dapper 会将其重写为 WHERE id IN (@Ids1, @Ids2, @Ids3),驱动程序随后会转换每个展开后的参数。 ClickHouse 的 has() 也支持配合 Array 参数使用:

自定义类型处理器

某些 ClickHouse 类型 (如 ITupleBigIntegerClickHouseDecimal) 需要在启动时注册相应的处理器:
有关类型处理程序实现的示例,请参见 Dapper 示例

Dapper.Contrib

GetAll<T>()Get<T>(id) 可以正常工作。Insert<T>() 不支持——它会生成 SQL Server 语法 (SCOPE_IDENTITY[]) 。建议改用 ClickHouseClient 原生的 InsertBinaryAsync 方法。
属性名称必须与 ClickHouse 列名完全一致 (区分大小写) 。

局限性

Linq2db

此驱动与 linq2db 兼容;后者是适用于 .NET 的轻量级 ORM 和 LINQ 提供商。详细文档请参见项目网站。 示例用法: 使用 ClickHouse 提供商创建 DataConnection
表映射可以通过特性或 Fluent API 配置来定义。如果类名和属性名与表名和列名完全一致,则无需配置:
查询:
批量复制: 使用 BulkCopyAsync 可高效执行批量插入。

Entity Framework Core

ClickHouse 官方的 Entity Framework Core 提供商。可将 C# 类映射到 ClickHouse 表,使用 LINQ 进行查询,并通过 SaveChanges 插入数据——全部采用熟悉的 EF Core 模式。
该提供商仍在积极开发中。当前版本支持 LINQ 查询 (包括 JOIN、子查询和集合运算) 、通过 SaveChanges / BulkInsertAsync 执行 INSERT、支持完整 DDL (CREATE / ALTER / DROP) 的迁移,以及 ClickHouse 特有的表引擎配置。不支持 UPDATE / DELETE

安装

需要 .NET 10.0 和 EF Core 10。

快速入门

定义实体和 DbContext,然后使用 LINQ 查询:

支持的类型

在需要 Decimal128/Decimal256 列的完整精度时,请使用 ClickHouseDecimal (来自 ClickHouse.Driver.Numerics) ,而不是 decimal——.NET 的 decimal 仅支持 28–29 位有效数字。

支持的 LINQ 操作

查询: Where, OrderBy, Take, Skip, Select, First, Single, Any, All, Count, Distinct, AsNoTracking GROUP BY 与聚合: GroupBy 配合 Count, LongCount, Sum, Average, Min, Max —— 包括 HAVING (在 .GroupBy() 之后调用 .Where()) 、在单个投影中使用多个聚合,以及按聚合结果执行 OrderBy JOIN: Join (INNER) 、GroupJoin/SelectMany 模式 (LEFT 和 CROSS) 。对于不匹配的行,LEFT JOIN 会返回实际的 null (参见下方的 LEFT JOIN null 语义) 。 子查询: 关联 Contains / INAny / EXISTSAll,以及投影中的标量子查询。 集合操作: Concat (→ UNION ALL) 、Union (→ UNION DISTINCT) 、IntersectExcept 内联本地集合: 针对内存中集合 (int[]List<T> 等) 的联接和 Contains 会被转换为一系列 UNION。 字符串方法: Contains, StartsWith, EndsWith, IndexOf, Replace, Substring, Trim/TrimStart/TrimEnd, ToLower, ToUpper, Length, IsNullOrEmpty, Concat (以及 + 运算符) 。 数学函数: 标准 MathMathF 方法会被转换为对应的 ClickHouse 函数 —— 包括算术、对数、三角和实用函数。
LEFT JOIN 的 NULL 语义
该提供程序会自动在每条连接路径中注入 set_join_use_nulls=1,以使 JOIN 行为符合 Entity Framework 的预期。 如果你的 ClickHouse 服务器或 profile 禁止更改此设置 (例如 readonly=1 profile) ,可通过以下方式禁用:
启用 opt-out 后,LEFT JOIN 会返回 ClickHouse 列的默认值,EF 基于 null 的导航属性检测将不再按预期工作。请显式与 0 / "" 比较,不要使用 == null

插入数据

SaveChanges 使用驱动程序提供的原生 InsertBinaryAsync API——采用 RowBinary 编码和 GZip 压缩,相比参数化 SQL 效率高得多:
实体在保存后会从 Added 状态变为 Unchanged,与其他 EF Core 提供商一致。 批次大小可配置 (默认值为 1000) :

批量插入

对于高吞吐量的数据加载,请使用 BulkInsertAsync 而不是 SaveChanges。这是 DbContext 上的一个扩展方法,会完全绕过 EF Core 的更改跟踪、标识解析和状态管理,转而直接调用驱动的 InsertBinaryAsync,并使用 RowBinary 编码和 GZip 压缩。 因此,它非常适合加载大型数据集,尤其是在插入后不需要跟踪实体的场景下:
输入可以是任意 IEnumerable<T>——它会以流式方式处理这些实体,无需将它们全部加载到内存中。返回值为插入的行数。插入后,实体不会附加到 DbContext,因此不会发生 AddedUnchanged 状态转换。

枚举

ClickHouse Enum8/Enum16 列可映射为 string 属性或 C# enum 类型。使用 C# 枚举时,提供商会自动在枚举值及其字符串表示形式之间进行转换:

自定义类型转换

EF Core 的 ValueConverter 系统允许你将自定义类型映射到提供商已支持的类型。提供商不会直接看到你的自定义类型——EF Core 会在边界处完成转换。 针对单个属性的转换:
可重用的转换器类:

列类型注解

对于 stringintDateTime 等标量类型,提供商会自动推断出 ClickHouse 类型。对于参数化类型和包装类型,则需要显式指定 ClickHouse 类型。 使用数据注解 (attribute) :
OnModelCreating 中使用 Fluent API:
支持 Array(Nullable(Int32))LowCardinality(Nullable(String)) 这类嵌套包装类型——提供程序会在每一层嵌套中自动解开 NullableLowCardinality

Variant 和 Dynamic 列

ClickHouse Variant(T1, T2, ...)Dynamic 列在 .NET 中会映射为 object。由于 object 过于宽泛,无法自动推断类型,因此必须通过 .HasColumnType() 显式声明存储类型:
读取时,该值会根据存储的判别器自动反序列化为相应的 .NET 类型 (例如 stringulongulong[]) 。

JSON 列

该提供程序支持 ClickHouse 的 Json 列类型,可映射到 System.Text.Json.Nodes.JsonNode (主要) 或 string (通过自动 ValueConverter) :
JSON 的读取和写入既可通过 SaveChanges,也可通过 BulkInsertAsync 完成:
如果你更喜欢原始 JSON 字符串,可将该属性映射为 string,并将列类型设为 Json——提供程序会自动应用 ValueConverter
  • 不支持 JSON 路径转换 — LINQ 中的 entity.Data["name"] 不会转换为 ClickHouse 的 data.name SQL 语法。请对非 JSON 列进行过滤,并在内存中检查 JSON 内容。
  • NULL 语义 — 对于 NULL 值,ClickHouse 的 JSON 类型返回的是 {} (空对象) ,而不是 SQL NULL。
  • 整数精度 — ClickHouse JSON 会将所有整数存储为 Int64。通过 JsonNode 读取时,应使用 GetValue<long>(),而不是 GetValue<int>()

表引擎

通过 ToTable(name, t => ...) 流式 API 配置 ClickHouse 表引擎及引擎特定子句。若未配置引擎,提供商默认使用 MergeTree,并根据实体的主键确定 ORDER BY
支持的引擎系列: 引擎子句: WithOrderBy, WithPartitionBy, WithPrimaryKey, WithSampleBy, WithTtl, WithSettings。它们都会附加到 HasXxxEngine() 返回的引擎构建器上。 列级功能: HasCodec, HasTtl, HasComment, HasDefault —— 都会纳入迁移。 数据跳过索引 —— 通过 HasIndex(...).HasSkippingIndexType(...)
普通 (非跳过型) 索引会被静默忽略,因为 ClickHouse 没有对应的实现。唯一索引则会抛出异常,因为 ClickHouse 不强制保证唯一性。

迁移

EF Core 的标准迁移工作流:
支持的操作:

迁移限制

除迁移外,该提供商目前还不支持:
  • UPDATE / DELETE
  • 事务BeginTransaction 是空操作。ClickHouse 不支持 ACID 事务。
  • JSON 路径查询转换:LINQ 中的 entity.Data["key"] 不会转换为 ClickHouse 的 data.key SQL 语法。请对非 JSON 列进行过滤,并在内存中检查 JSON。

局限性

AggregateFunction 列

无法直接查询或插入 AggregateFunction(...) 类型的列。 如需插入:
要进行查询:

最后修改于 2026年7月2日