メインコンテンツへスキップ
メインコンテンツへスキップ

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

使用方法

接続文字列パラメータ

ParameterDescriptionDefault
HostClickHouse サーバーアドレスlocalhost
PortClickHouse サーバーポート8123 または 8443Protocol に依存)
Database初期データベースdefault
Username認証ユーザー名default
Password認証パスワード(空)
Protocol接続プロトコル(http または httpshttp
CompressionGzip 圧縮を有効化true
UseSession永続的なサーバーセッションを有効化false
SessionIdカスタムセッション IDランダムな GUID
TimeoutHTTP タイムアウト(秒)120
UseServerTimezonedatetime 列にサーバータイムゾーンを使用true
UseCustomDecimals小数に ClickHouseDecimal を使用false

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

セッション

UseSession フラグを有効にすると、サーバーセッションが永続化され、SET 文や一時テーブルを使用できるようになります。セッションは 60 秒間アクティビティがない場合(デフォルトのタイムアウト)にリセットされます。セッションの有効期間は、ClickHouse ステートメントでセッション設定を行うことで延長できます。

ClickHouseConnection クラスは通常、並列実行(複数スレッドによる同時クエリ実行)を許可します。しかし、UseSession フラグを有効にすると、任意の時点で 1 接続あたり 1 つのアクティブなクエリに制限されます(サーバー側の制約です)。


接続の有効期間とプーリング

ClickHouse.Driver は内部的に System.Net.Http.HttpClient を使用しています。HttpClient はエンドポイントごとに接続プールを持ちます。その結果:

  • ClickHouseConnection オブジェクトは TCP 接続と 1:1 で対応していません。複数のデータベースセッションは、サーバーごとに複数の(デフォルトでは 2 本の)TCP 接続上で多重化されます。
  • ClickHouseConnection オブジェクトが破棄された後も、接続が維持される場合があります。
  • この挙動は、カスタムの HttpClientHandler を指定した独自の HttpClient を渡すことで調整できます。

DI 環境向けには、HTTP クライアントの設定を共通化できる専用コンストラクター ClickHouseConnection(string connectionString, IHttpClientFactory httpClientFactory, string httpClientName = "") が用意されています。

推奨事項:

  • ClickHouseConnection はサーバーとの「セッション」を表します。サーバーのバージョンを問い合わせることで機能検出を行うため(接続オープン時にわずかなオーバーヘッドがあります)が、一般的にはこのオブジェクトを何度も生成・破棄しても問題ありません。
  • コネクションの推奨ライフタイムは、複数のクエリにまたがる大きな「トランザクション」あたり 1 つの接続オブジェクトとすることです。接続の開始時にはわずかなオーバーヘッドがあるため、クエリごとに接続オブジェクトを生成することは推奨されません。
  • アプリケーションが大量のトランザクションを扱い、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 プロパティ)
  • データソース(IDataReader または IEnumerable<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 プロパティで列名を任意に指定できます。
  • 設定可能なパラメータ: Columns, BatchSize, MaxDegreeOfParallelism
  • コピーを行う前に、対象テーブルの構造情報を取得するために SELECT * FROM <table> LIMIT 0 クエリが実行されます。指定するオブジェクトの型は、対象テーブルの型と概ね一致している必要があります。
  • セッションは並列挿入と互換性がありません。ClickHouseBulkCopy に渡す接続ではセッションを無効にするか、MaxDegreeOfParallelism1 に設定する必要があります。

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」例外が発生する可能性があります。
  • レコードを大量に挿入する場合は、Bulk Insert 機能の利用を検討してください。

サポートされているデータ型

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) → 任意の型を要素とする Array
  • Tuple(T1, T2, ...) → 任意の型を要素とする Tuple
  • Nullable(T) → 任意の型の Nullable
  • 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 の抽象 API と統合されており、軽量なオプトイン方式のロギングを提供します。ロギングを有効にすると、ドライバーは接続ライフサイクルイベント、コマンド実行、トランスポート処理、およびバルクコピーアップロードに対して構造化されたメッセージを出力します。ロギングは完全に任意であり、ロガーを構成していないアプリケーションでも追加のオーバーヘッドなしに動作し続けます。

クイックスタート

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

カテゴリと出力元

このドライバーは専用のカテゴリを使用しており、コンポーネントごとにログレベルをきめ細かく調整できます。

CategorySourceHighlights
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 クライアントファクトリの選択(デフォルトプールか単一接続か)
  • HTTP ハンドラの設定(SocketsHttpHandler または HttpClientHandler)
  • 接続プールの設定(MaxConnectionsPerServer、PooledConnectionLifetime など)
  • タイムアウトの設定(ConnectTimeout、Expect100ContinueTimeout など)
  • SSL/TLS 設定
  • 接続のオープン/クローズ イベント
  • セッション ID の追跡

デバッグモード: ネットワークトレースと診断

ネットワークに関する問題の診断を支援するために、ドライバーライブラリには .NET のネットワーク内部処理を低レベルでトレースできるヘルパー機能が含まれています。これを有効にするには、ログレベルを Trace に設定した LoggerFactory を渡し、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 }
);