跳至主要内容
跳至主要内容

ClickHouse C# 客户端

用于连接 ClickHouse 的官方 C# 客户端。客户端源代码可在 GitHub 仓库 中找到。最初由 Oleg V. Kozlyuk 开发。

迁移指南

  1. 使用新的包名称 ClickHouse.DriverNuGet 上的最新版本 更新你的 .csproj 文件。
  2. 在你的代码库中,将所有 ClickHouse.Client 引用更新为 ClickHouse.Driver

支持的 .NET 版本

ClickHouse.Driver 支持以下 .NET 版本

  • .NET Framework 4.6.2
  • .NET Framework 4.8
  • .NET Standard 2.1
  • .NET 6.0
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

安装

从 NuGet 安装包

dotnet add package ClickHouse.Driver

或者使用 NuGet 包管理器

Install-Package ClickHouse.Driver

快速入门

using ClickHouse.Driver.ADO;

using (var connection = new ClickHouseConnection("Host=my.clickhouse;Protocol=https;Port=8443;Username=user"))
{
    var version = await connection.ExecuteScalarAsync("SELECT version()");
    Console.WriteLine(version);
}

配置

有两种方式配置你与 ClickHouse 的连接

  • 连接字符串: 分号分隔的键/值对,用于指定主机、身份验证凭据和其他连接选项。
  • ClickHouseClientSettings 对象: 一个强类型配置对象,可以从配置文件加载或在代码中设置。

以下是所有设置、默认值及其影响的完整列表。

连接设置

属性类型默认值连接字符串键描述
Hoststring"localhost"HostClickHouse 服务器的主机名或 IP 地址
端口ushort8123 (HTTP) / 8443 (HTTPS)端口端口号;默认值基于协议
用户名string"default"用户名身份验证用户名
密码string""密码身份验证密码
数据库string""数据库默认数据库;空值使用服务器/用户默认值
协议string"http"协议连接协议:"http""https"
路径stringnull路径反向代理场景的 URL 路径(例如,/clickhouse
超时TimeSpan2 分钟超时操作超时(在连接字符串中存储为秒)

数据格式 & 序列化

属性类型默认值连接字符串键描述
UseCompressionbooltrue压缩启用 gzip 压缩进行数据传输
UseCustomDecimalsbooltrueUseCustomDecimals使用 ClickHouseDecimal 进行任意精度;如果为 false,则使用 .NET decimal(128 位限制)
UseFormDataParametersboolfalseUseFormDataParameters将参数作为表单数据而不是 URL 查询字符串发送

会话管理

属性类型默认值连接字符串键描述
UseSessionboolfalseUseSession启用有状态会话;序列化请求
SessionIdstringnullSessionId会话 ID;如果 UseSession 为 true 且为 null,则自动生成 GUID
注意

UseSession 标志启用服务器会话的持久性,允许使用 SET 语句和临时表。会话将在 60 秒不活动后重置(默认超时)。可以通过 ClickHouse 语句或服务器配置来延长会话生命周期。

启用 UseSession 标志会将活动查询限制为每个连接的单个查询(这是服务器端限制)。

安全性

属性类型默认值连接字符串键描述
SkipServerCertificateValidationboolfalse跳过 HTTPS 证书验证;不用于生产环境

HTTP 客户端配置

属性类型默认值连接字符串键描述
HttpClientHttpClientnull自定义预配置的 HttpClient 实例
HttpClientFactoryIHttpClientFactorynull用于创建 HttpClient 实例的自定义工厂
HttpClientNamestringnull用于 HttpClientFactory 创建特定客户端的名称

日志记录 & 调试

属性类型默认值连接字符串键描述
LoggerFactoryILoggerFactorynull用于诊断日志记录的记录器工厂
EnableDebugModeboolfalse启用 .NET 网络跟踪(需要 LoggerFactory 将级别设置为 Trace);显著影响性能

自定义设置 & 角色

属性类型默认值连接字符串键描述
CustomSettingsIDictionary<string, object>set_* 前缀ClickHouse 服务器设置,请参阅下面的说明。
RolesIReadOnlyList<string>Roles逗号分隔的 ClickHouse 角色(例如,Roles=admin,reader
注意

在使用连接字符串设置自定义设置时,请使用 set_ 前缀,例如 "set_max_threads=4"。在使用 ClickHouseClientSettings 对象时,请不要使用 set_ 前缀。

有关可用设置的完整列表,请参阅 此处


连接字符串示例

基本连接

Host=localhost;Port=8123;Username=default;Password=secret;Database=mydb

使用自定义 ClickHouse 设置

Host=localhost;set_max_threads=4;set_readonly=1;set_max_memory_usage=10000000000

用法

连接

要连接到 ClickHouse,请使用连接字符串或 ClickHouseClientSettings 对象创建一个 ClickHouseConnection。有关可用选项,请参阅 配置 部分。

你的 ClickHouse Cloud 服务的详细信息可在 ClickHouse Cloud 控制台中找到。

选择一个服务并单击 Connect

选择 C#。将显示连接详细信息。

如果您使用的是自托管 ClickHouse,则连接详细信息由您的 ClickHouse 管理员设置。

使用连接字符串

using ClickHouse.Driver.ADO;

using var connection = new ClickHouseConnection("Host=localhost;Username=default;Password=secret");
await connection.OpenAsync();

或者使用 ClickHouseClientSettings

var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    Username = "default",
    Password = "secret"
};
using var connection2 = new ClickHouseConnection(settings);
await connection2.OpenAsync();
注意
  • 一个 ClickHouseConnection 代表与服务器的“会话”。它通过查询服务器版本执行功能发现(因此打开时存在轻微的开销),但通常可以多次创建和销毁此类对象。
  • 连接的推荐生命周期是每个大型“事务”跨多个查询的一个连接对象。ClickHouseConnection 对象可以长期存在。底层 TCP 连接将由连接池回收。
  • 如果应用程序处理大量事务并需要频繁创建/销毁 ClickHouseConnection 对象,建议使用 IHttpClientFactory 或静态 HttpClient 实例来管理连接。

创建表

使用标准 SQL 语法创建一个表

using ClickHouse.Driver.ADO;

using (var connection = new ClickHouseConnection(connectionString))
{
    await connection.OpenAsync();

    using (var command = connection.CreateCommand())
    {
        command.CommandText = "CREATE TABLE IF NOT EXISTS default.my_table (id Int64, name String) ENGINE = Memory";
        await command.ExecuteNonQueryAsync();
    }
}

插入数据

使用参数化查询插入数据

using ClickHouse.Driver.ADO;

using (var connection = new ClickHouseConnection(connectionString))
{
    await connection.OpenAsync();

    using (var command = connection.CreateCommand())
    {
        command.AddParameter("id", "Int64", 1);
        command.AddParameter("name", "String", "test");
        command.CommandText = "INSERT INTO default.my_table (id, name) VALUES ({id:Int64}, {name:String})";
        await command.ExecuteNonQueryAsync();
    }
}

批量插入

使用 ClickHouseBulkCopy 插入大量行。它使用 ClickHouse 的本机行二进制格式有效地流式传输数据,并行工作并将数据分成批处理。它还避免了由于参数集过大而导致的“URL 过长”错误。

使用 ClickHouseBulkCopy 需要

  • 目标连接(ClickHouseConnection 实例)
  • 目标表名(DestinationTableName 属性)
  • 数据源(IDataReaderIEnumerable<object[]>
using ClickHouse.Driver.ADO;
using ClickHouse.Driver.Copy;

using var connection = new ClickHouseConnection(connectionString);
await connection.OpenAsync();

using var bulkCopy = new ClickHouseBulkCopy(connection)
{
    DestinationTableName = "default.my_table",
    BatchSize = 100000,
    MaxDegreeOfParallelism = 2
};

await bulkCopy.InitAsync(); // Prepares ClickHouseBulkCopy instance by loading target column types

var values = Enumerable.Range(0, 1000000)
    .Select(i => new object[] { (long)i, "value" + i });

await bulkCopy.WriteToServerAsync(values);
Console.WriteLine($"Rows written: {bulkCopy.RowsWritten}");
注意
  • 为了获得最佳性能,ClickHouseBulkCopy 使用任务并行库 (TPL) 处理数据批处理,最多有 4 个并行插入任务(可以进行调整)。
  • 如果源数据比目标表中的列少,可以通过 ColumnNames 属性可选地提供列名。
  • 可配置参数:ColumnsBatchSizeMaxDegreeOfParallelism
  • 在复制之前,将执行 SELECT * FROM <table> LIMIT 0 查询以获取目标表结构的详细信息。提供的对象的类型必须与目标表合理匹配。
  • 会话与并行插入不兼容。传递给 ClickHouseBulkCopy 的连接必须禁用会话,或者 MaxDegreeOfParallelism 必须设置为 1

执行 SELECT 查询

使用 ExecuteReader()ExecuteReaderAsync() 执行 SELECT 查询。返回的 DbDataReader 通过 GetInt64()GetString()GetFieldValue<T>() 等方法提供对结果列的类型化访问。

调用 Read() 以前进到下一行。当没有更多行时,它返回 false。按索引(从 0 开始)或按列名访问列。

using ClickHouse.Driver.ADO;
using System.Data;

using (var connection = new ClickHouseConnection(connectionString))
{
    await connection.OpenAsync();

    using (var command = connection.CreateCommand())
    {
        command.AddParameter("id", "Int64", 10);
        command.CommandText = "SELECT * FROM default.my_table WHERE id < {id:Int64}";
        using var reader = await command.ExecuteReaderAsync();
        while (reader.Read())
        {
            Console.WriteLine($"select: Id: {reader.GetInt64(0)}, Name: {reader.GetString(1)}");
        }
    }
}

SQL 参数

在 ClickHouse 中,SQL 查询中参数的标准格式为 {parameter_name:DataType}

示例

SELECT {value:Array(UInt16)} as a
SELECT * FROM table WHERE val = {tuple_in_tuple:Tuple(UInt8, Tuple(String, UInt8))}
INSERT INTO table VALUES ({val1:Int32}, {val2:Array(UInt8)})
注意

SQL “绑定”参数作为 HTTP URI 查询参数传递,因此使用过多的参数可能会导致“URL 过长”异常。使用 ClickHouseBulkInsert 可以绕过此限制。


查询 ID

执行每个查询的方法也会在结果中包含一个 query_id。此唯一标识符由客户端为每个查询分配,可用于从 system.query_log 表(如果已启用)中获取数据,或取消长时间运行的查询。如果需要,可以在 ClickHouseCommand 对象中覆盖查询 ID。

var customQueryId = $"qid-{Guid.NewGuid()}";

using var command = connection.CreateCommand();
command.CommandText = "SELECT version()";
command.QueryId = customQueryId;

var version = await command.ExecuteScalarAsync();
Console.WriteLine($"QueryId: {command.QueryId}");
提示

如果您正在覆盖 QueryId 参数,则需要确保每次调用它的唯一性。随机 GUID 是一个不错的选择。


原始流式传输

可以绕过数据读取器直接流式传输特定格式的数据。这在希望以特定格式将数据保存到文件中的情况下很有用。例如

using var command = connection.CreateCommand();
command.CommandText = "SELECT * FROM default.my_table LIMIT 100 FORMAT JSONEachRow";
using var result = await command.ExecuteRawResultAsync(CancellationToken.None);
using var stream = await result.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
var json = await reader.ReadToEndAsync();

原始流式插入

使用 InsertRawStreamAsync 直接从文件或内存流中以 CSV、JSON 或任何 支持的 ClickHouse 格式 插入数据。

从 CSV 文件插入

await using var fileStream = File.OpenRead("data.csv");

using var response = await connection.InsertRawStreamAsync(
    table: "my_table",
    stream: fileStream,
    format: "CSV",
    columns: ["id", "product", "price"]); // Optional: specify columns
注意

有关控制数据导入行为的选项,请参阅 格式设置文档


更多示例

有关其他实际用法示例,请参阅 GitHub 仓库中的 examples 目录

最佳实践

连接生命周期和连接池

ClickHouse.Driver 在底层使用 System.Net.Http.HttpClientHttpClient 具有每个端点的连接池。因此

  • ClickHouseConnection 对象与 TCP 连接没有 1:1 映射 - 多个数据库会话将通过每个服务器的几个 TCP 连接多路复用。
  • ClickHouseConnection 对象可以长期存在;底层的 TCP 连接将由连接池回收。
  • HttpClient 内部管理连接池。不要自己池化 ClickHouseConnection 对象。
  • ClickHouseConnection 对象已释放后,连接可以保持活动状态。
  • 可以通过传递自定义 HttpClientFactory 或具有自定义 HttpClientHandlerHttpClient 来调整此行为。

对于 DI 环境,有一个定制的构造函数 ClickHouseConnection(string connectionString, IHttpClientFactory httpClientFactory, string httpClientName = ""),它使 ClickHouseConnection 请求一个命名的 http 客户端。

参考

在使用自定义 HttpClientHttpClientFactory 时,请确保将 PooledConnectionIdleTimeout 设置为小于服务器 keep_alive_timeout 的值,以避免由于半关闭连接而导致的错误。Cloud 部署的默认 keep_alive_timeout 为 10 秒。


DateTime 处理

  1. 尽可能使用 UTC。 将时间戳存储为 DateTime('UTC') 列,并在代码中使用 DateTimeKind.Utc。 这消除了时区歧义。

  2. 对于显式时区处理,请使用 DateTimeOffset 它始终表示一个特定的时刻,并包含偏移信息。

  3. 在 HTTP 参数类型提示中指定时区。 当使用针对非 UTC 列的 Unspecified DateTime 值的参数时

    command.AddParameter("dt", value, "DateTime('Europe/Amsterdam')");
    

异步插入

异步插入 将批处理的责任从客户端转移到服务器。 不再需要客户端批处理,服务器会缓冲传入的数据,并根据可配置的阈值将其刷新到存储中。 这对于高并发场景(例如可观察性工作负载,其中许多代理发送小负载)非常有用。

通过 CustomSettings 或连接字符串启用异步插入

// Using CustomSettings
var settings = new ClickHouseClientSettings("Host=localhost");
settings.CustomSettings["async_insert"] = 1;
settings.CustomSettings["wait_for_async_insert"] = 1; // Recommended: wait for flush acknowledgment

// Or via connection string
// "Host=localhost;set_async_insert=1;set_wait_for_async_insert=1"

两种模式(由 wait_for_async_insert 控制)

模式行为用例
wait_for_async_insert=1插入在数据刷新到磁盘后返回。 错误会返回给客户端。推荐 用于大多数工作负载
wait_for_async_insert=0插入在数据缓冲后立即返回。 不保证数据会被持久化。仅当数据丢失是可以接受时
注意

使用 wait_for_async_insert=0 时,错误仅在刷新期间出现,并且无法追溯到原始插入。 客户端也无法提供反压,从而可能导致服务器过载。

关键设置

设置描述
async_insert_max_data_size当缓冲区达到此大小时(字节)刷新
async_insert_busy_timeout_ms在此超时后刷新(毫秒)
async_insert_max_query_number累积到这么多查询后刷新

会话

仅在需要有状态服务器端功能时才启用会话,例如:

  • 临时表 (CREATE TEMPORARY TABLE)
  • 在多个语句之间维护查询上下文
  • 会话级别设置 (SET max_threads = 4)

启用会话后,请求将被序列化以防止并发使用相同的会话。 这会为不需要会话状态的工作负载增加开销。

var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    UseSession = true,
    SessionId = "my-session", // Optional -- will be auto-generated if not provided
};

await using var connection = new ClickHouseConnection(settings);
await connection.OpenAsync();

await using var cmd1 = connection.CreateCommand("CREATE TEMPORARY TABLE temp_ids (id UInt64)");
await cmd1.ExecuteNonQueryAsync();

await using var cmd2 = connection.CreateCommand("INSERT INTO temp_ids VALUES (1), (2), (3)");
await cmd2.ExecuteNonQueryAsync();

await using var cmd3 = connection.CreateCommand("SELECT * FROM users WHERE id IN (SELECT id FROM temp_ids)");
await using var reader = await cmd3.ExecuteReaderAsync();

支持的数据类型

ClickHouse.Driver 支持所有 ClickHouse 数据类型。 下面的表格显示了读取数据库数据时 ClickHouse 类型与本机 .NET 类型之间的映射。

类型映射:从 ClickHouse 读取

整数类型

ClickHouse 类型.NET 类型
Int8sbyte
UInt8byte
Int16short
UInt16ushort
Int32int
UInt32uint
Int64long
UInt64ulong
Int128BigInteger
UInt128BigInteger
Int256BigInteger
UInt256BigInteger

浮点类型

ClickHouse 类型.NET 类型
Float32float
Float64double
BFloat16float

十进制类型

ClickHouse 类型.NET 类型
Decimal(P, S)decimal / ClickHouseDecimal
Decimal32(S)decimal / ClickHouseDecimal
Decimal64(S)decimal / ClickHouseDecimal
Decimal128(S)decimal / ClickHouseDecimal
Decimal256(S)decimal / ClickHouseDecimal
注意

十进制类型转换由 UseCustomDecimals 设置控制。


布尔类型

ClickHouse 类型.NET 类型
Boolbool

字符串类型

ClickHouse 类型.NET 类型
Stringstring
FixedString(N)byte[]

日期和时间类型

ClickHouse 类型.NET 类型
DateDateTime
Date32DateTime
DateTimeDateTime
DateTime32DateTime
DateTime64DateTime
TimeTimeSpan
Time64TimeSpan

ClickHouse 内部将 DateTimeDateTime64 值存储为 Unix 时间戳(自纪元以来的秒或亚秒单位)。 虽然存储始终以 UTC 为单位,但列可以关联一个时区,该时区会影响值的显示和解释方式。

读取 DateTime 值时,DateTime.Kind 属性基于列的时区设置

列定义返回的 DateTime.Kind注意事项
DateTime('UTC')Utc显式 UTC 时区
DateTime('Europe/Amsterdam')Unspecified偏移已应用
DateTimeUnspecified保留壁钟时间

对于非 UTC 列,返回的 DateTime 表示该时区中的壁钟时间。 使用 ClickHouseDataReader.GetDateTimeOffset() 获取具有该时区正确偏移的 DateTimeOffset

var reader = (ClickHouseDataReader)await connection.ExecuteReaderAsync(
    "SELECT toDateTime('2024-06-15 14:30:00', 'Europe/Amsterdam')");
reader.Read();

var dt = reader.GetDateTime(0);    // 2024-06-15 14:30:00, Kind=Unspecified
var dto = reader.GetDateTimeOffset(0); // 2024-06-15 14:30:00 +02:00 (CEST)

对于没有显式时区(即,DateTime 而不是 DateTime('Europe/Amsterdam'))的列,驱动程序返回一个 DateTime,其 Kind=Unspecified。 这会精确地保留存储的壁钟时间,而不会对时区做出任何假设。

如果您需要对没有显式时区的列进行时区感知行为,请执行以下操作:

  1. 在列定义中使用显式时区:DateTime('UTC')DateTime('Europe/Amsterdam')
  2. 在读取后自行应用时区。

其他类型

ClickHouse 类型.NET 类型
UUIDGuid
IPv4IPAddress
IPv6IPAddress
NothingDBNull
Dynamic请参阅注释
JsonJsonObject
Array(T)T[]
Tuple(T1, T2, ...)Tuple<T1, T2, ...> / LargeTuple
Map(K, V)Dictionary<K, V>
Nullable(T)T?
Enum8string
Enum16string
LowCardinality(T)与 T 相同
SimpleAggregateFunction与底层类型相同
Nested(...)Tuple[]
Variant(T1, T2, ...)请参阅注释
QBit(T, dimension)T[]
注意

Dynamic 和 Variant 类型将转换为每行实际底层类型的相应类型。


几何类型

ClickHouse 类型.NET 类型
PointTuple<double, double>
RingTuple<double, double>[]
LineStringTuple<double, double>[]
PolygonRing[]
MultiLineStringLineString[]
MultiPolygonPolygon[]
Geometry请参阅注释
注意

Geometry 类型是一种 Variant 类型,可以容纳任何几何类型。 它将被转换为相应的类型。


类型映射:写入 ClickHouse

在插入数据时,驱动程序将 .NET 类型转换为相应的 ClickHouse 类型。 下面的表格显示了每个 ClickHouse 列类型接受的 .NET 类型。

整数类型

ClickHouse 类型接受的 .NET 类型注意事项
Int8sbyte,任何 Convert.ToSByte() 兼容的类型
UInt8byte,任何 Convert.ToByte() 兼容的类型
Int16short,任何 Convert.ToInt16() 兼容的类型
UInt16ushort,任何 Convert.ToUInt16() 兼容的类型
Int32int,任何 Convert.ToInt32() 兼容的类型
UInt32uint,任何 Convert.ToUInt32() 兼容的类型
Int64long,任何 Convert.ToInt64() 兼容的类型
UInt64ulong,任何 Convert.ToUInt64() 兼容的类型
Int128BigInteger, decimal, double, float, int, uint, long, ulong,任何 Convert.ToInt64() 兼容的类型
UInt128BigInteger, decimal, double, float, int, uint, long, ulong,任何 Convert.ToInt64() 兼容的类型
Int256BigInteger, decimal, double, float, int, uint, long, ulong,任何 Convert.ToInt64() 兼容的类型
UInt256BigInteger, decimal, double, float, int, uint, long, ulong,任何 Convert.ToInt64() 兼容的类型

浮点类型

ClickHouse 类型接受的 .NET 类型注意事项
Float32float,任何 Convert.ToSingle() 兼容的类型
Float64double,任何 Convert.ToDouble() 兼容的类型
BFloat16float,任何 Convert.ToSingle() 兼容的类型截断为 16 位脑浮点格式

布尔类型

ClickHouse 类型接受的 .NET 类型注意事项
Boolbool

字符串类型

ClickHouse 类型接受的 .NET 类型注意事项
Stringstring,任何 Convert.ToString() 兼容的类型
FixedString(N)string, byte[]字符串采用 UTF-8 编码并填充/截断;byte[] 必须正好是 N 个字节

日期和时间类型

ClickHouse 类型接受的 .NET 类型注意事项
DateDateTime, DateTimeOffset, DateOnly, NodaTime 类型转换为 Unix 天数作为 UInt16
Date32DateTime, DateTimeOffset, DateOnly, NodaTime 类型转换为 Unix 天数作为 Int32
DateTimeDateTime, DateTimeOffset, DateOnly, NodaTime 类型有关详细信息,请参阅下文
DateTime32DateTime, DateTimeOffset, DateOnly, NodaTime 类型与 DateTime 相同
DateTime64DateTime, DateTimeOffset, DateOnly, NodaTime 类型基于 Scale 参数的精度
TimeTimeSpan, int限制为 ±999:59:59;int 被视为秒
Time64TimeSpan, decimal, double, float, int, long, string字符串解析为 [-]HHH:MM:SS[.fraction];限制为 ±999:59:59.999999999

驱动程序在写入值时会尊重 DateTime.Kind

DateTime.Kind行为
Utc精确保留瞬间
本地使用系统时区转换为 UTC,保留瞬间
Unspecified按目标列的时区处理壁钟时间

DateTimeOffset 值始终保留精确的瞬间。

示例:UTC DateTime(瞬间保留)

var utcTime = new DateTime(2024, 1, 15, 12, 0, 0, DateTimeKind.Utc);
// Stored as 12:00 UTC
// Read from DateTime('Europe/Amsterdam') column: 13:00 (UTC+1)
// Read from DateTime('UTC') column: 12:00 UTC

示例:未指定的 DateTime(壁钟时间)

var wallClock = new DateTime(2024, 1, 15, 14, 30, 0, DateTimeKind.Unspecified);
// Written to DateTime('Europe/Amsterdam') column: stored as 14:30 Amsterdam time
// Read back from DateTime('Europe/Amsterdam') column: 14:30

建议: 为了获得最简单和最可预测的行为,请对所有 DateTime 操作使用 DateTimeKind.UtcDateTimeOffset。 这可确保您的代码无论服务器时区、客户端时区或列时区如何都能始终如一地工作。

HTTP 参数与批量复制

在写入 Unspecified DateTime 值时,HTTP 参数绑定和批量复制之间存在重要差异

批量复制 知道目标列的时区,并正确解释该时区中的 Unspecified 值。

HTTP 参数 无法自动知道列的时区。 您必须在参数类型提示中指定它

// CORRECT: Timezone in type hint
command.AddParameter("dt", myDateTime, "DateTime('Europe/Amsterdam')");
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime('Europe/Amsterdam')})";

// INCORRECT: Without timezone hint, interpreted as UTC
command.AddParameter("dt", myDateTime);
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime})";
// String value "2024-01-15 14:30:00" interpreted as UTC, not Amsterdam time!
DateTime.Kind目标列HTTP 参数(带有 tz 提示)HTTP 参数(没有 tz 提示)批量复制
UtcUTC保留瞬间保留瞬间保留瞬间
UtcEurope/Amsterdam保留瞬间保留瞬间保留瞬间
本地任何保留瞬间保留瞬间保留瞬间
UnspecifiedUTC视为 UTC视为 UTC视为 UTC
UnspecifiedEurope/Amsterdam视为阿姆斯特丹时间视为 UTC视为阿姆斯特丹时间

十进制类型

ClickHouse 类型接受的 .NET 类型注意事项
Decimal(P,S)decimal, ClickHouseDecimal,任何 Convert.ToDecimal() 兼容的类型如果超出精度,则抛出 OverflowException
Decimal32decimal, ClickHouseDecimal,任何 Convert.ToDecimal() 兼容的类型最大精度 9
Decimal64decimal, ClickHouseDecimal,任何 Convert.ToDecimal() 兼容的类型最大精度 18
Decimal128decimal, ClickHouseDecimal,任何 Convert.ToDecimal() 兼容的类型最大精度 38
Decimal256decimal, ClickHouseDecimal,任何 Convert.ToDecimal() 兼容的类型最大精度 76

其他类型

ClickHouse 类型接受的 .NET 类型注意事项
UUIDGuid, string将字符串解析为 Guid
IPv4IPAddress, string必须是 IPv4;字符串通过 IPAddress.Parse() 解析
IPv6IPAddress, string必须是 IPv6;字符串通过 IPAddress.Parse() 解析
Nothing任何不执行任何操作(无操作)
Dynamic不支持(抛出 NotImplementedException
Jsonstring, JsonObject, 任何对象将字符串解析为 JSON;对象通过 JsonSerializer 序列化
Array(T)IList, nullNull 写入空数组
Tuple(T1, T2, ...)ITuple, IList元素计数必须与元组基数匹配
Map(K, V)IDictionary
Nullable(T)nullDBNull 或 T 接受的类型在值之前写入 null 标志字节
Enum8stringsbyte、数值类型在枚举字典中查找的字符串
Enum16stringshort、数值类型在枚举字典中查找的字符串
LowCardinality(T)T 接受的类型委托给底层类型
SimpleAggregateFunction底层类型接受的类型委托给底层类型
Nested(...)IList 的元组元素计数必须与字段计数匹配
Variant(T1, T2, ...)与 T1、T2、... 中的一个值匹配如果没有类型匹配则抛出 ArgumentException
QBit(T, dim)IList委托给 Array;维度仅为元数据

几何类型

ClickHouse 类型接受的 .NET 类型注意事项
PointSystem.Drawing.PointITupleIList(2 个元素)
RingIList 的点
LineStringIList 的点
PolygonIList 的环
MultiLineStringIList 的 LineStrings
MultiPolygonIList 的多边形
Geometry以上任何几何类型所有几何类型的变体

不支持写入

ClickHouse 类型注意事项
Dynamic抛出 NotImplementedException
AggregateFunction抛出 AggregateFunctionException

嵌套类型处理

ClickHouse 嵌套类型(Nested(...))可以使用数组语义进行读取和写入。

CREATE TABLE test.nested (
    id UInt32,
    params Nested (param_id UInt8, param_val String)
) ENGINE = Memory
using var bulkCopy = new ClickHouseBulkCopy(connection)
{
    DestinationTableName = "test.nested"
};

var row1 = new object[] { 1, new[] { 1, 2, 3 }, new[] { "v1", "v2", "v3" } };
var row2 = new object[] { 2, new[] { 4, 5, 6 }, new[] { "v4", "v5", "v6" } };

await bulkCopy.WriteToServerAsync(new[] { row1, row2 });

日志记录和诊断

ClickHouse .NET 客户端与 Microsoft.Extensions.Logging 抽象集成,以提供轻量级、选择加入的日志记录。启用后,驱动程序会为连接生命周期事件、命令执行、传输操作和批量复制上传发出结构化消息。日志记录完全是可选的——不配置记录器的应用程序将继续运行而不会产生额外的开销。

快速入门

使用 ClickHouseConnection

using ClickHouse.Driver.ADO;
using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Information);
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

await using var connection = new ClickHouseConnection(settings);
await connection.OpenAsync();

使用 appsettings.json

您可以使用标准的 .NET 配置来配置日志级别

using ClickHouse.Driver.ADO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .Build();

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(configuration.GetSection("Logging"))
        .AddConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

await using var connection = new ClickHouseConnection(settings);
await connection.OpenAsync();

使用内存配置

您还可以通过代码按类别配置日志详细程度

using ClickHouse.Driver.ADO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var categoriesConfiguration = new Dictionary<string, string>
{
    { "LogLevel:Default", "Warning" },
    { "LogLevel:ClickHouse.Driver.Connection", "Information" },
    { "LogLevel:ClickHouse.Driver.Command", "Debug" }
};

var config = new ConfigurationBuilder()
    .AddInMemoryCollection(categoriesConfiguration)
    .Build();

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(config)
        .AddSimpleConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

await using var connection = new ClickHouseConnection(settings);
await connection.OpenAsync();

类别和发射器

驱动程序使用专用类别,以便您可以微调每个组件的日志级别

类别来源亮点
ClickHouse.Driver.ConnectionClickHouseConnection连接生命周期、HTTP 客户端工厂选择、连接打开/关闭、会话管理。
ClickHouse.Driver.CommandClickHouseCommand查询执行开始/完成、计时、查询 ID、服务器统计信息和错误详细信息。
ClickHouse.Driver.TransportClickHouseConnection低级 HTTP 流式请求、压缩标志、响应状态代码和传输故障。
ClickHouse.Driver.BulkCopyClickHouseBulkCopy元数据加载、批量操作、行计数和上传完成。
ClickHouse.Driver.NetTraceTraceHelper网络跟踪,仅在调试模式下启用

示例:诊断连接问题

{
    "Logging": {
        "LogLevel": {
            "ClickHouse.Driver.Connection": "Trace",
            "ClickHouse.Driver.Transport": "Trace"
        }
    }
}

这将记录

  • HTTP 客户端工厂选择(默认池与单个连接)
  • HTTP 处理程序配置(SocketsHttpHandler 或 HttpClientHandler)
  • 连接池设置(MaxConnectionsPerServer、PooledConnectionLifetime 等)
  • 超时设置(ConnectTimeout、Expect100ContinueTimeout 等)
  • SSL/TLS 配置
  • 连接打开/关闭事件
  • 会话 ID 跟踪

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

为了帮助诊断网络问题,驱动程序库包含一个助手,可以启用 .NET 网络内部的低级跟踪。要启用它,您必须传递一个 LoggerFactory,其级别设置为 Trace,并将 EnableDebugMode 设置为 true(或通过 ClickHouse.Driver.Diagnostic.TraceHelper 类手动启用它)。事件将记录到 ClickHouse.Driver.NetTrace 类别。警告:这将生成极其详细的日志,并影响性能。不建议在生产环境中启用调试模式。

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace); // Must be Trace level to see network events
});

var settings = new ClickHouseClientSettings()
{
    LoggerFactory = loggerFactory,
    EnableDebugMode = true,  // Enable low-level network tracing
};

OpenTelemetry

驱动程序通过 .NET System.Diagnostics.Activity API 提供对 OpenTelemetry 分布式跟踪的内置支持。启用后,驱动程序会为可以导出到可观察性后端(如 Jaeger 或 ClickHouse 本身(通过 OpenTelemetry Collector))的数据库操作发出跨度。

启用跟踪

在 ASP.NET Core 应用程序中,将 ClickHouse 驱动程序的 ActivitySource 添加到您的 OpenTelemetry 配置

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)  // Subscribe to ClickHouse driver spans
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());             // Or AddJaegerExporter(), etc.

对于控制台应用程序、测试或手动设置

using OpenTelemetry;
using OpenTelemetry.Trace;

var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)
    .AddConsoleExporter()
    .Build();

跨度属性

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

属性描述
db.system始终为 "clickhouse"
db.name数据库名称
db.user用户名
db.statementSQL 查询(如果启用)
db.clickhouse.read_rows查询读取的行数
db.clickhouse.read_bytes查询读取的字节数
db.clickhouse.written_rows查询写入的行数
db.clickhouse.written_bytes查询写入的字节数
db.clickhouse.elapsed_ns服务器端执行时间,以纳秒为单位

配置选项

通过 ClickHouseDiagnosticsOptions 控制跟踪行为

using ClickHouse.Driver.Diagnostic;

// Include SQL statements in spans (default: false for security)
ClickHouseDiagnosticsOptions.IncludeSqlInActivityTags = true;

// Truncate long SQL statements (default: 1000 characters)
ClickHouseDiagnosticsOptions.StatementMaxLength = 500;
注意

启用 IncludeSqlInActivityTags 可能会在您的跟踪中暴露敏感数据。在生产环境中使用时请谨慎。

TLS 配置

通过 HTTPS 连接到 ClickHouse 时,您可以以多种方式配置 TLS/SSL 行为。

自定义证书验证

对于需要自定义证书验证逻辑的生产环境,请提供配置了 ServerCertificateCustomValidationCallback 处理程序的自定义 HttpClient

using System.Net;
using System.Net.Security;
using ClickHouse.Driver.ADO;

var handler = new HttpClientHandler
{
    // Required when compression is enabled (default)
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,

    ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
    {
        // Example: Accept a specific certificate thumbprint
        if (cert?.Thumbprint == "YOUR_EXPECTED_THUMBPRINT")
            return true;

        // Example: Accept certificates from a specific issuer
        if (cert?.Issuer.Contains("YourOrganization") == true)
            return true;

        // Default: Use standard validation
        return sslPolicyErrors == SslPolicyErrors.None;
    },
};

var httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromMinutes(5) };

var settings = new ClickHouseClientSettings
{
    Host = "my.clickhouse.server",
    Protocol = "https",
    HttpClient = httpClient,
};

using var connection = new ClickHouseConnection(settings);
await connection.OpenAsync();
注意

提供自定义 HttpClient 时的重要注意事项

  • 自动解压缩:如果未禁用压缩(默认情况下启用压缩),则必须启用 AutomaticDecompression
  • 空闲超时:将 PooledConnectionIdleTimeout 设置为小于服务器的 keep_alive_timeout(ClickHouse Cloud 为 10 秒)以避免来自半打开连接的连接错误。

ORM 支持

Dapper

ClickHouse.Driver 可以与 Dapper 一起使用,但不支持匿名对象。

工作示例

connection.QueryAsync<string>(
    "SELECT {p1:Int32}",
    new Dictionary<string, object> { { "p1", 42 } }
);

不支持

connection.QueryAsync<string>(
    "SELECT {p1:Int32}",
    new { p1 = 42 }
);

Linq2db

此驱动程序与 linq2db 兼容,linq2db 是 .NET 的轻量级 ORM 和 LINQ 提供程序。有关详细文档,请参阅项目网站。

示例用法

使用 ClickHouse 提供程序创建一个 DataConnection

using LinqToDB;
using LinqToDB.Data;
using LinqToDB.DataProvider.ClickHouse;

var connectionString = "Host=localhost;Port=8123;Database=default";
var options = new DataOptions()
    .UseClickHouse(connectionString, ClickHouseProvider.ClickHouseDriver);

await using var db = new DataConnection(options);

可以使用属性或流式配置定义表映射。如果您的类和属性名称与表和列名称完全匹配,则不需要配置

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

查询

await using var db = new DataConnection(options);

var products = await db.GetTable<Product>()
    .Where(p => p.Price > 100)
    .OrderByDescending(p => p.Name)
    .ToListAsync();

批量复制

使用 BulkCopyAsync 进行高效的批量插入。

await using var db = new DataConnection(options);
var table = db.GetTable<Product>();

var options = new BulkCopyOptions
{
    MaxBatchSize = 100000,
    MaxDegreeOfParallelism = 1,
    WithoutSession = true
};

await table.BulkCopyAsync(options, products);

Entity framework core

目前不支持 Entity Framework Core。

限制

AggregateFunction 列

类型为 AggregateFunction(...) 的列不能直接查询或插入。

要插入

INSERT INTO t VALUES (uniqState(1));

要选择

SELECT uniqMerge(c) FROM t;

    © . This site is unofficial and not affiliated with ClickHouse, Inc.