跳到主要内容
跳到主要内容

ClickHouse C# 客户端

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

迁移指南

  1. .csproj 文件中将包名更新为 ClickHouse.Driver,并将版本更新为 NuGet 上的最新版本
  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);
}

使用 Dapper

using Dapper;
using ClickHouse.Driver.ADO;

using (var connection = new ClickHouseConnection("Host=my.clickhouse"))
{
    var result = await connection.QueryAsync<string>("SELECT name FROM system.databases");
    Console.WriteLine(string.Join('\n', result));
}

使用方法

连接字符串参数

参数描述默认值
HostClickHouse 服务器地址localhost
PortClickHouse 服务器端口81238443(取决于 Protocol
Database初始数据库default
Username身份验证用户名default
Password身份验证密码(空)
Protocol连接协议(httphttpshttp
Compression启用 Gzip 压缩true
UseSession启用服务器会话持久化false
SessionId自定义会话 ID随机 GUID
TimeoutHTTP 超时时间(秒)120
UseServerTimezone对 datetime 列使用服务器的时区true
UseCustomDecimals对小数使用 ClickHouseDecimalfalse

示例: Host=clickhouse;Port=8123;Username=default;Password=;Database=default

Sessions

UseSession 选项启用服务器会话持久化,从而可以使用 SET 语句和临时表。会话在 60 秒无活动后将被重置(默认超时时间)。可以通过在 ClickHouse 语句中设置会话相关配置来延长会话生命周期。

ClickHouseConnection 类通常允许并行操作(多个线程可以并发执行查询)。但是,启用 UseSession 选项后,在任意时刻每个连接只能有一个活动查询(服务器端限制)。


连接生命周期与连接池

ClickHouse.Driver 在底层使用 System.Net.Http.HttpClientHttpClient 会针对每个端点维护一个连接池。因此:

  • 一个 ClickHouseConnection 对象并不是与 TCP 连接一一对应——多个数据库会话会通过每台服务器上的多个(默认 2 个)TCP 连接复用。
  • 即使 ClickHouseConnection 对象已被释放,连接依然可能保持存活。
  • 可以通过传入一个带有自定义 HttpClientHandler 的定制 HttpClient 来调整此行为。

对于依赖注入(DI)环境,提供了一个专用构造函数 ClickHouseConnection(string connectionString, IHttpClientFactory httpClientFactory, string httpClientName = ""),用于统一配置 HTTP 客户端设置。

建议:

  • 一个 ClickHouseConnection 表示与服务器的一个“会话”。它会通过查询服务器版本进行特性探测(因此在打开连接时会有少量开销),但总体而言,多次创建和销毁此类对象是安全的。
  • 建议的连接生命周期是:对一个跨多个查询的大型“事务”使用一个连接对象。由于连接建立存在少量开销,不建议为每个查询单独创建一个连接对象。
  • 如果应用程序处理大量事务,并且需要频繁创建/销毁 ClickHouseConnection 对象,建议使用 IHttpClientFactory 或一个静态的 HttpClient 实例来管理连接。

创建表

使用标准 SQL 语法创建表:

using ClickHouse.Driver.ADO;

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

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

插入数据

使用参数化查询插入数据:

using ClickHouse.Driver.ADO;

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

    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})";
        command.ExecuteNonQuery();
    }
}

批量插入

使用 ClickHouseBulkCopy 时需要:

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

using var connection = new ClickHouseConnection(connectionString);
connection.Open();

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 使用 Task Parallel Library (TPL) 处理批量数据,最多可并行执行 4 个插入任务(此值可调)。
  • 如果源数据的列数少于目标表的列数,可以通过 ColumnNames 属性可选地指定列名。
  • 可配置参数:ColumnsBatchSizeMaxDegreeOfParallelism
  • 在复制之前,会执行 SELECT * FROM <table> LIMIT 0 查询以获取目标表结构信息。要写入的对象类型必须与目标表结构合理匹配。
  • 会话与并行插入不兼容。传递给 ClickHouseBulkCopy 的连接必须禁用会话,或者将 MaxDegreeOfParallelism 设置为 1

执行 SELECT 查询

执行 SELECT 查询并处理其结果:

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

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

原始数据流

using var command = connection.CreateCommand();
command.Text = "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 = reader.ReadToEnd();

嵌套列支持

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 });

AggregateFunction 列

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

要进行插入:

INSERT INTO t VALUES (uniqState(1));

选择:

SELECT uniqMerge(c) FROM t;

SQL 参数

要在查询中传递参数,必须使用 ClickHouse 的参数格式,形式如下:

{<name>:<data type>}

示例:

SELECT {value:Array(UInt16)} as value
SELECT * FROM table WHERE val = {tuple_in_tuple:Tuple(UInt8, Tuple(String, UInt8))}
INSERT INTO table VALUES ({val1:Int32}, {val2:Array(UInt8)})
注意
  • SQL bind 参数作为 HTTP URI 查询参数传递,因此如果使用过多,可能会触发 “URL too long” 异常。
  • 若要插入大量记录,请考虑使用批量插入功能。

支持的数据类型

ClickHouse.Driver 支持以下 ClickHouse 数据类型及其对应的 .NET 类型映射:

布尔类型

  • Boolbool

数值类型

有符号整数:

  • Int8sbyte
  • Int16short
  • Int32int
  • Int64long
  • Int128BigInteger
  • Int256BigInteger

无符号整数:

  • UInt8byte
  • UInt16ushort
  • UInt32uint
  • UInt64ulong
  • UInt128BigInteger
  • UInt256BigInteger

浮点数:

  • Float32float
  • Float64double

Decimal 类型:

  • Decimaldecimal
  • Decimal32decimal
  • Decimal64decimal
  • Decimal128decimal
  • Decimal256BigDecimal

字符串类型

  • Stringstring
  • FixedStringstring

日期时间类型

  • DateDateTime
  • Date32DateTime
  • DateTimeDateTime
  • DateTime32DateTime
  • DateTime64DateTime

网络类型

  • IPv4IPAddress
  • IPv6IPAddress

地理数据类型

  • PointTuple
  • RingArray of Points
  • PolygonArray of Rings

复杂类型

  • Array(T)任意类型的数组
  • Tuple(T1, T2, ...)任意类型的元组
  • Nullable(T)任意类型的可空版本
  • Map(K, V)Dictionary<K, V>

DateTime 处理

ClickHouse.Driver 会正确处理时区和 DateTime.Kind 属性。具体来说:

  • DateTime 值以 UTC 返回。用户可以自行进行转换,或者在 DateTime 实例上使用 ToLocalTime() 方法。
  • 插入时,DateTime 值按如下方式处理:
    • UTCDateTime 会按原样插入,因为 ClickHouse 在内部以 UTC 存储它们。
    • LocalDateTime 会根据用户本地时区设置转换为 UTC。
    • UnspecifiedDateTime 会被视为处于目标列对应的时区,因此会根据该时区转换为 UTC。
  • 对于未指定时区的列,默认使用客户端时区(兼容旧行为)。可以在连接字符串中使用 UseServerTimezone 标志来改用服务器时区。

日志和诊断

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元数据加载、批量操作、行计数以及上传完成情况。

示例:排查连接问题

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

这将记录:

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

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

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

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace); // 必须设为 Trace 级别以查看网络事件
});

var settings = new ClickHouseClientSettings()
{
    LoggerFactory = loggerFactory,
    EnableDebugMode = true,  // 启用底层网络追踪
};

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 }
);