ClickHouse に接続するための公式 C# クライアントです。 クライアントのソースコードは GitHubリポジトリ で公開されています。 当初は Oleg V. Kozlyuk によって開発されました。
このライブラリは、主に 2 つの 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
サポートされている ClickHouse バージョン
このクライアントは、直近3つのリリースと直近2つのLTSリリースを公式にサポートしています。
インストール
NuGet からパッケージをインストールします。
dotnet add package ClickHouse.Driverまたは、NuGet パッケージ マネージャーを使用します。
Install-Package ClickHouse.Driverクイックスタート
using ClickHouse.Driver;
// クライアントを作成する(通常はシングルトンとして)
using var client = new ClickHouseClient("Host=my.clickhouse;Protocol=https;Port=8443;Username=user");
// クエリを実行する
var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine(version);設定
ClickHouse への接続を設定する方法は 2 つあります。
- 接続文字列: ホスト、認証情報、その他の接続オプションを指定する、セミコロン区切りのキーと値のペアです。
ClickHouseClientSettingsobject: 設定ファイルから読み込むことも、コード内で設定することもできる、厳密に型付けされた設定オブジェクトです。
以下に、すべての設定項目、そのデフォルト値、およびそれぞれの動作への影響を一覧で示します。
接続設定
| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
|---|---|---|---|---|
| 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 カラムを string ではなく byte[] として読み取ります。バイナリデータに便利です |
| UseFormDataParameters | bool |
false |
UseFormDataParameters |
パラメータを URL のクエリ文字列ではなくフォームデータとして送信します |
| ReadBufferSize | int |
8192 (8 KiB) |
ReadBufferSize |
HTTP クエリ応答の読み取りに使用するバッファのサイズ (バイト単位) です。大きな result sets でのバッファ再補充を減らすには値を増やしてください。85,000 バイト以上の値は Large Object Heap に割り当てられるため、LOH の断片化や GC の停止時間の長期化を引き起こす可能性があります。 |
| ParameterTypeResolver | IParameterTypeResolver |
null |
— | @ スタイルのパラメータ型対応に使用するカスタムリゾルバです。カスタムパラメータ型対応 を参照してください |
| ParameterFormatter | IParameterFormatter |
null |
— | パラメータ値のシリアライゼーションに使用するカスタムフォーマッタです。カスタムパラメータ値フォーマット を参照してください |
| ReadValueConverter | IReadValueConverter |
null |
— | データリーダーが返す値に適用するカスタム変換です。カスタム読み取り値変換 を参照してください |
| 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 が自動生成されます |
セキュリティ
| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
|---|---|---|---|---|
| SkipServerCertificateValidation | bool |
false |
— | HTTPS 証明書の検証をスキップします。本番環境では使用しないでください。 |
HTTP クライアントの構成
| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
|---|---|---|---|---|
| HttpClient | HttpClient |
null |
— | カスタムの事前構成済み HttpClient インスタンス |
| HttpClientFactory | IHttpClientFactory |
null |
— | HttpClient インスタンスを作成するためのカスタム ファクトリ |
| HttpClientName | string |
null |
— | HttpClientFactory で特定のクライアントを作成する際の名前 |
ログとデバッグ
| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
|---|---|---|---|---|
| LoggerFactory | ILoggerFactory |
null |
— | 診断ログ用のロガーファクトリ |
| EnableDebugMode | bool |
false |
— | .NET のネットワーク トレースを有効にします (Trace レベルに設定された LoggerFactory が必要) 。パフォーマンスへの影響が大きい |
カスタム設定とロール
| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
|---|---|---|---|---|
| CustomSettings | IDictionary<string, object> |
空 | set_* プレフィックス |
ClickHouse のサーバー設定。以下の注記を参照してください |
| Roles | IReadOnlyList<string> |
空 | Roles |
カンマ区切りの ClickHouse ロール (例: Roles=admin,reader) |
| ApplicationInfo | IReadOnlyDictionary<string, string> |
空 | — | アプリケーションごとのクエリの帰属先を識別するため、HTTP User-Agent ヘッダーに追加される自由形式のタグ。 |
接続文字列の例
基本接続
Host=localhost;Port=8123;Username=default;Password=secret;Database=mydbカスタムのClickHouse設定を使用する場合
Host=localhost;set_max_threads=4;set_readonly=1;set_max_memory_usage=10000000000QueryOptions
QueryOptions を使用すると、クライアント レベルの設定をクエリごとに上書きできます。すべてのプロパティは省略可能で、指定した場合にのみクライアントのデフォルト値を上書きします。
| プロパティ | 型 | 説明 |
|---|---|---|
| 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 |
このクエリの認証トークンを上書きします |
| ParameterTypeResolver | IParameterTypeResolver |
@ 形式のパラメータ型対応に対するクライアント レベルのリゾルバを上書きします。詳細は カスタム パラメータ型対応 を参照してください |
| ParameterFormatter | IParameterFormatter |
@ 形式のパラメータ値のシリアライゼーションに対するクライアント レベルのフォーマッタを上書きします。詳細は カスタム パラメータ値のフォーマット を参照してください |
| ReadValueConverter | IReadValueConverter |
データ リーダーから返される値に適用されるクライアント レベルの変換を上書きします。詳細は カスタム読み取り値変換 を参照してください |
| MaxExecutionTime | TimeSpan? |
サーバー側のクエリ タイムアウト (max_execution_time 設定として渡されます) 。制限を超えると、サーバーがクエリをキャンセルします |
| AcceptEncoding | string |
クエリごとの Accept-Encoding ヘッダー上書き (例: "zstd"、"zstd, gzip;q=0.5")。また、URL で enable_http_compression=1 も強制します。詳細は クエリごとの転送圧縮 を参照してください。 |
例:
var options = new QueryOptions
{
QueryId = "report-2024-001",
Database = "analytics",
CustomSettings = new Dictionary<string, object>
{
{ "max_threads", 4 },
{ "max_memory_usage", 10_000_000_000 }
},
MaxExecutionTime = TimeSpan.FromMinutes(5)
};
var reader = await client.ExecuteReaderAsync(
"SELECT * FROM large_table",
parameters: null,
options: options
);InsertOptions
InsertOptions は、InsertBinaryAsync による一括 insert 操作に固有の設定を追加して、QueryOptions を拡張したものです。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
| BatchSize | int |
100,000 | Batch ごとの行数 |
| MaxDegreeOfParallelism | int |
1 | 並列 Batch アップロード数 |
| Format | RowBinaryFormat |
RowBinary |
バイナリフォーマット: RowBinary または RowBinaryWithDefaults |
| ColumnTypes | IReadOnlyDictionary<string, string> |
null |
カラム名 → ClickHouse 型文字列。設定すると、スキーマプローブクエリをスキップします。 |
| UseSchemaCache | bool |
false |
クライアントのライフタイム中、(database, table) ごとに完全なテーブルスキーマを cache します。 |
QueryOptions のすべてのプロパティは、InsertOptions でも利用できます。
例:
var insertOptions = new InsertOptions
{
BatchSize = 50_000,
MaxDegreeOfParallelism = 4,
QueryId = "bulk-import-001"
};
long rowsInserted = await client.InsertBinaryAsync(
"my_table",
columns,
rows,
insertOptions
);スキーマプローブクエリのスキップ
デフォルトでは、InsertBinaryAsync は各 insert の前に SELECT ... WHERE 1=0 クエリを送信し、カラムの型を特定します。高スループットが求められる場合は、2 つの方法でこのオーバーヘッドをなくせます。
オプション 1: カラム型を明示的に指定する
コンパイル時点でテーブルのスキーマがわかっている場合は、ColumnTypes で直接指定します。これにより、スキーマプローブクエリは一切送信されません。
var options = new InsertOptions
{
ColumnTypes = new Dictionary<string, string>
{
["id"] = "UInt64",
["name"] = "Nullable(String)",
["score"] = "Float32",
},
};
await client.InsertBinaryAsync("my_table", ["id", "name", "score"], rows, options);オプション 2: スキーマをキャッシュする
同じテーブルに繰り返し insert する場合は、UseSchemaCache = true を設定すると、スキーマのクエリは最初の 1 回だけで済み、同じ ClickHouseClient インスタンスでの以降の insert に再利用されます:
var options = new InsertOptions { UseSchemaCache = true };
// 最初の呼び出しでサーバーからスキーマを取得する
await client.InsertBinaryAsync("my_table", columns, batch1, options);
// 2回目の呼び出しではキャッシュ済みスキーマを再利用する — 追加のラウンドトリップなし
await client.InsertBinaryAsync("my_table", columns, batch2, options);ClickHouseClient
ClickHouseClient は、ClickHouse と連携するための推奨 API です。スレッドセーフで、シングルトンとして利用することを前提に設計されており、HTTP 接続プーリングも内部で管理します。
クライアントの作成
接続文字列または ClickHouseClientSettings オブジェクトを使用して、ClickHouseClient を作成します。利用可能なオプションについては、設定 セクションを参照してください。
ClickHouse Cloud サービスの詳細は、ClickHouse Cloud コンソールで確認できます。
サービスを選択し、Connect をクリックします。

C# を選択します。接続情報が下に表示されます。

セルフマネージドの ClickHouse を使用している場合、接続情報は ClickHouse 管理者が設定します。
接続文字列を使用する場合:
using ClickHouse.Driver;
using var client = new ClickHouseClient("Host=localhost;Username=default;Password=secret");または、ClickHouseClientSettings を使用します。
using ClickHouse.Driver;
var settings = new ClickHouseClientSettings
{
Host = "localhost",
Username = "default",
Password = "secret"
};
using var client = new ClickHouseClient(settings);依存関係の注入のシナリオでは、IHttpClientFactory を使用します。
// In your DI configuration
services.AddHttpClient("ClickHouse", client =>
{
client.Timeout = TimeSpan.FromMinutes(5);
}).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});
// Create client with factory
var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var client = new ClickHouseClient("Host=localhost", factory, "ClickHouse");クエリの実行
結果を返さないステートメントには ExecuteNonQueryAsync を使用します:
// テーブルを作成する
await client.ExecuteNonQueryAsync(
"CREATE TABLE IF NOT EXISTS default.my_table (id Int64, name String) ENGINE = Memory"
);
// テーブルを削除する
await client.ExecuteNonQueryAsync("DROP TABLE IF EXISTS default.my_table");単一の値を取得するには、ExecuteScalarAsync を使用します:
var count = await client.ExecuteScalarAsync("SELECT count() FROM default.my_table");
Console.WriteLine($"行数: {count}");
var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine($"サーバーバージョン: {version}");データの挿入
パラメーター化された挿入
ExecuteNonQueryAsync を使用して、パラメーター化クエリでデータを挿入します。パラメーターの型は、SQL 内で {name:Type} 構文を使って指定する必要があります。
using ClickHouse.Driver;
using ClickHouse.Driver.ADO.Parameters;
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("id", 1L);
parameters.AddParameter("name", "Alice");
await client.ExecuteNonQueryAsync(
"INSERT INTO default.my_table (id, name) VALUES ({id:Int64}, {name:String})",
parameters
);一括挿入
大量の行を効率よく挿入するには、InsertBinaryAsync を使用します。これは ClickHouse のネイティブな行バイナリ形式でデータをストリーミングし、バッチの並列アップロードをサポートするとともに、パラメーター化クエリで発生することがある「URL が長すぎる」エラーを回避します。
// IEnumerable<object[]> としてデータを準備する
var rows = Enumerable.Range(0, 1_000_000)
.Select(i => new object[] { (long)i, $"value{i}" });
var columns = new[] { "id", "name" };
// 基本的な挿入
long rowsInserted = await client.InsertBinaryAsync("default.my_table", columns, rows);
Console.WriteLine($"Rows inserted: {rowsInserted}");大規模なデータセットでは、InsertOptions を使用してバッチ処理と並列度を設定します:
var options = new InsertOptions
{
BatchSize = 100_000, // バッチあたりの行数(デフォルト: 100,000)
MaxDegreeOfParallelism = 4 // 並列バッチアップロード数(デフォルト: 1)
};POCO の挿入
object[] 配列を組み立てる代わりに、厳密に型付けされた POCO オブジェクトを直接 insert できます。型を一度登録したら、あとは IEnumerable<T> を渡します:
// テーブルのカラムに対応するPOCOを定義する
public class SensorReading
{
public ulong Id { get; set; }
public string SensorName { get; set; }
public double Value { get; set; }
public DateTime Timestamp { get; set; }
}
// 型を登録する(クライアントのライフタイムにつき1回)
client.RegisterBinaryInsertType<SensorReading>();
// 直接挿入する — カラム名はプロパティ名から導出される
var readings = Enumerable.Range(0, 100_000)
.Select(i => new SensorReading
{
Id = (ulong)i,
SensorName = $"sensor_{i % 10}",
Value = Random.Shared.NextDouble() * 100,
Timestamp = DateTime.UtcNow,
});
long rowsInserted = await client.InsertBinaryAsync("sensors", readings);既定では、公開されているすべての読み取り可能なプロパティは、厳密な大文字と小文字の区別を伴う名前一致によりカラムにマッピングされます。属性を使用して、このマッピングをカスタマイズできます。
public class Event
{
[ClickHouseColumn(Name = "event_id")] // 異なる名前のカラムにマッピングする
public ulong Id { get; set; }
[ClickHouseColumn(Type = "LowCardinality(String)")] // ClickHouseの型を明示的に指定
public string Category { get; set; }
public string Payload { get; set; }
[ClickHouseNotMapped] // insertから除外
public string InternalTag { get; set; }
}| 属性 | 目的 |
|---|---|
[ClickHouseColumn(Name = "...")] |
対象のカラム名を上書きする |
[ClickHouseColumn(Type = "...")] |
ClickHouse の型を明示的に指定する |
[ClickHouseNotMapped] |
挿入対象からそのプロパティを除外する |
マップされたすべてのプロパティで Type が明示的に指定されている場合、スキーマプローブクエリは完全にスキップされます。一部のプロパティにしか明示的な型が指定されていない場合、ドライバーはカラム一式に対するスキーマプローブにフォールバックします。
InsertBinaryAsync<T> は、object[] オーバーロードと同じ InsertOptions (バッチ化、並列度、スキーマキャッシュ) をサポートします。
スキーマ進化
型の登録後にターゲットテーブルへカラムが追加されても、POCO による insert はそのまま問題なく動作します。ドライバーが insert するのは POCO にマッピングされたカラムだけなので、DEFAULT (またはその他のデフォルト式) を持つ新しいカラムはサーバー側で自動的に補完されます。コードを変更したり、再登録したりする必要はありません。
データの読み取り
SELECT クエリの実行には ExecuteReaderAsync を使用します。返される ClickHouseDataReader では、GetInt64()、GetString()、GetFieldValue<T>() などのメソッドを使って、結果カラムに型付きでアクセスできます。
次の行に進むには Read() を呼び出します。これ以上行がない場合は false を返します。カラムには、インデックス (0 始まり) またはカラム名でアクセスできます。
using ClickHouse.Driver.ADO.Parameters;
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("max_id", 100L);
var reader = await client.ExecuteReaderAsync(
"SELECT * FROM default.my_table WHERE id < {max_id:Int64}",
parameters
);
while (reader.Read())
{
Console.WriteLine($"Id: {reader.GetInt64(0)}, Name: {reader.GetString(1)}");
}POCO の読み取り
カラムをインデックスや名前で読み取る代わりに、クエリ結果を独自のクラスへ直接ストリームできます。型をクライアントに一度登録すれば、QueryAsync<T> を使用できます。
// Define a POCO matching your result columns
public class SensorReading
{
public ulong Id { get; set; }
public DateTime Timestamp { get; set; }
[ClickHouseColumn(Name = "sensor_name")]
public string SensorName { get; set; }
public double Value { get; set; }
}
// Register the type (once per client lifetime)
client.RegisterPocoType<SensorReading>();
// Stream results as typed objects
await foreach (var reading in client.QueryAsync<SensorReading>(
"SELECT Id, sensor_name, Value, Timestamp FROM sensors"))
{
Console.WriteLine($"{reading.SensorName}: {reading.Value}");
}登録
RegisterPocoType<T>() は、insert と read の両方のマッピングを設定し、両方を事前に検証します。RegisterBinaryInsertType<T>() に変更はなく、backwards compatibility のため引き続き insert 専用です。
登録する型は、次の条件を満たしている必要があります。
- public の引数なしコンストラクター
- public で
initではない setter を持つ public プロパティが少なくとも 1 つあること。requiredプロパティもサポートされます。
カラムのマッチング
カラムのマッチングでは大文字と小文字が区別されます。結果に存在しないカラムについては、対応するプロパティはデフォルト値のままとなり、余分な結果カラムは無視されます。
自動変換は行われず、型が一致しない場合は InvalidOperationException がスローされます。
1 行をマテリアライズする
リーダーを手動で順に処理する場合は、ClickHouseDataReader.MapTo<T>() を使用して、リーダーを進めずに現在の行を登録済みの POCO にマテリアライズします。
var reader = await client.ExecuteReaderAsync("SELECT Id, SensorName, Value, Timestamp FROM sensors");
while (reader.Read())
{
SensorReading reading = reader.MapTo<SensorReading>();
Console.WriteLine($"{reading.SensorName}: {reading.Value}");
}登録時の診断情報
LoggerFactory が設定されている場合、RegisterPocoType<T>() と RegisterBinaryInsertType<T>() は、どのプロパティがどのカラムにマッピングされたか、またどのプロパティがなぜスキップされたのかを示す Debug レベルのログ (カテゴリ ClickHouse.Driver.Client) を出力します。詳しくは、ロギングと診断情報を参照してください。
SQLパラメータ
ClickHouse では、SQLクエリのクエリパラメータの標準的なフォーマットは {parameter_name:DataType} です。
例:
SELECT {value:Array(UInt16)} as aSELECT * FROM table WHERE val = {tuple_in_tuple:Tuple(UInt8, Tuple(String, UInt8))}INSERT INTO table VALUES ({val1:Int32}, {val2:Array(UInt8)})Identifier パラメーター
Identifier パラメーター型を使用すると、引用符付きの文字列リテラルの代わりに、データベース名、テーブル名、またはカラム名を安全にバインドできます。SQL では {name:Identifier} 構文を使用するか、ClickHouseDbParameter.ClickHouseType = "Identifier" を設定して使用します。
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("name", "my_database");
await client.ExecuteNonQueryAsync("CREATE DATABASE {name:Identifier}", parameters);var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("col", "user_id");
var reader = await client.ExecuteReaderAsync("SELECT {col:Identifier} FROM t", parameters);値はそのまま送信され、サーバーがそれをクォートなしのSQL識別子として置き換え、サーバー側のバッククォートによる引用とエスケープを適用します。特殊文字 (バッククォートを含む) を含む識別子も、安全にラウンドトリップできます。
クエリ ID
すべてのクエリには一意の query_id が割り当てられます。これは、system.query_log テーブルからデータを取得したり、長時間実行中のクエリをキャンセルしたりする際に使用できます。QueryOptions でカスタムのクエリ ID を指定することもできます。
var options = new QueryOptions
{
QueryId = $"report-{Guid.NewGuid()}"
};
var reader = await client.ExecuteReaderAsync(
"SELECT * FROM large_table",
parameters: null,
options: options
);カスタム パラメータ型マッピング
@ 形式のパラメータ (例: WHERE id = @id) を使用すると、ドライバーは .NET の値型から ClickHouse の型を自動的に推論します。たとえば、int は Int32 にマッピングされます。
これらの既定の対応を上書きするには、ClickHouseClientSettings で ParameterTypeResolver を設定します。これは、個々のパラメータごとに ClickHouseType を設定しなくても、すべての DateTime パラメータでミリ秒精度の DateTime64(3) を使いたい場合や、すべての decimal で特定の小数点以下桁数を使いたい場合に便利です。
シンプルな型マッピングに DictionaryParameterTypeResolver を使用する:
using ClickHouse.Driver.ADO.Parameters;
var settings = new ClickHouseClientSettings("Host=localhost")
{
ParameterTypeResolver = new DictionaryParameterTypeResolver(new Dictionary<Type, string>
{
[typeof(DateTime)] = "DateTime64(3)",
[typeof(decimal)] = "Decimal64(4)",
}),
};
using var client = new ClickHouseClient(settings);
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("dt", DateTime.UtcNow); // Mapped to DateTime64(3)
parameters.AddParameter("amount", 99.1234m); // Mapped to Decimal64(4)
await client.ExecuteReaderAsync("SELECT @dt, @amount", parameters);高度な用途向けのカスタム IParameterTypeResolver:
値や名前に基づいて解決する場合は、IParameterTypeResolver インターフェイスを直接実装します。既定の推論に委ねるには、null を返します。
public class SmartDecimalResolver : IParameterTypeResolver
{
public string ResolveType(Type clrType, object value, string parameterName)
{
if (clrType != typeof(decimal))
return null; // Fall through to default
var scale = (decimal.GetBits((decimal)value)[3] >> 16) & 0x7F;
return scale <= 4 ? $"Decimal64({scale})" : $"Decimal128({scale})";
}
}単一のクエリに対しては、QueryOptions.ParameterTypeResolver を介してリゾルバを設定することもできます。設定した場合、クライアントレベルのリゾルバより優先されます。
型解決の優先順位:
リゾルバは優先順位チェーンの一要素です。優先度の高いものから低いものの順に示すと、次のとおりです。
- パラメータに明示的に設定された
ClickHouseType - クエリ内の
{name:Type}構文による SQL の型ヒント IParameterTypeResolver(QueryOptions.ParameterTypeResolverを使用し、未設定の場合はClickHouseClientSettings.ParameterTypeResolverにフォールバック)- 組み込みの型推論 (
TypeConverter.ToClickHouseType)
このリゾルバは、ADO.NET の ClickHouseConnection パスでも機能します。設定は、クライアントから作成された接続に引き継がれます。
カスタム パラメータ値のフォーマット
IParameterFormatter は、パラメータ値をどのようにシリアライズするかを決定するフックです。組み込みのフォーマット (例: DateTime の精度、小数のカルチャ、文字列のエスケープ、数値表現) が、スキーマや後続のツールの想定と一致しない場合に使用します。
パラメータ化されたすべてのクエリにフォーマッタを適用するには、ClickHouseClientSettings で ParameterFormatter を設定します。このフォーマッタは、値、解決された ClickHouse の型名、パラメータ名を受け取り、サーバーに送信される文字列表現を返します。既定のフォーマッタに処理を委ねるには、null を返します。
単純な CLR 型ごとのフォーマットには DictionaryParameterFormatter を使用します:
using ClickHouse.Driver.ADO.Parameters;
var settings = new ClickHouseClientSettings("Host=localhost")
{
ParameterFormatter = new DictionaryParameterFormatter(new Dictionary<Type, Func<object, string>>
{
[typeof(DateTime)] = v => ((DateTime)v).ToString("yyyy-MM-ddTHH:mm:ss.ffffff",
System.Globalization.CultureInfo.InvariantCulture),
[typeof(decimal)] = v => ((decimal)v).ToString("F4",
System.Globalization.CultureInfo.InvariantCulture),
}),
};
using var client = new ClickHouseClient(settings);高度なユースケース向けのカスタム IParameterFormatter:
public class FixedDecimalFormatter : IParameterFormatter
{
public string Format(object value, string typeName, string parameterName)
{
if (value is decimal d)
return d.ToString("F4", System.Globalization.CultureInfo.InvariantCulture);
return null; // Fall through for anything else
}
}QueryOptions.ParameterFormatter を使うと、クエリごとにフォーマッタを設定することもできます。設定した場合、クライアントレベルのフォーマッタより優先されます。
複合値:
このフォーマッタは、最上位のコレクション parameter と、複合値 (Array、Tuple、Map、Nullable、LowCardinality、Variant) 内の各要素の両方に対して実行されます。たとえば、typeof(int) のマッピングでは、Array(Int32) 内の各 Int32 要素が個別にフォーマットされます。
複合コンテキストでのシングルクォートによる囲み:
複合リテラル内に埋め込まれた文字列系の ClickHouse type (String、FixedString、Enum8、Enum16、IPv4、IPv6、UUID) では、ドライバーはフォーマッタの出力をシングルクォートで囲みますが、その内容はエスケープしません。返された文字列にエスケープされていないシングルクォートや backslash が含まれていると、複合リテラルは不正な形式となり、server はクエリを拒否します。
最上位の文字列 parameter (複合値に埋め込まれていないもの) は囲まずにそのまま使用されるため、その場合は escaping は不要です。
フォーマッタの優先順位:
IParameterFormatter(QueryOptions.ParameterFormatterを使用し、未設定の場合はClickHouseClientSettings.ParameterFormatterにフォールバック) 。これが non-null を返した場合は、その値が使用されます。HttpParameterFormatterに組み込まれている type 固有のフォーマット。
このフォーマッタは null または DBNull の値には適用されません。これらは常に ClickHouse の null センチネル (\N) として serialize されます。
カスタム読み取り値変換
IReadValueConverter を使用すると、CLR 型を変更せずに、データリーダーが返す値をデシリアライゼーション後に変換できます。一般的な用途としては、タイムゾーンを持たない DateTime カラムに対して DateTime.Kind = Utc を設定すること、文字列のトリミングや正規化を行うこと、あるいは JSON カラムがアプリケーションコードに渡される前に後処理することなどがあります。
すべての読み取りに対してコンバーターを適用するには、ClickHouseClientSettings で ReadValueConverter を設定します。コンバーターは、ボックス化された (GetValue) パスとジェネリック (GetFieldValue<T>) パスの両方で、各カラムの各行ごとに 1 回呼び出されます。コンバーターが設定されていない場合、オーバーヘッドはゼロで、リーダーは値をそのまま返します。
単純な CLR 型単位の変換に DictionaryReadValueConverter を使用する:
using ClickHouse.Driver.ADO.Readers;
var converter = new DictionaryReadValueConverter()
.For<DateTime>(dt => DateTime.SpecifyKind(dt, DateTimeKind.Utc))
.For<string>(s => s.Trim());
var settings = new ClickHouseClientSettings("Host=localhost")
{
ReadValueConverter = converter,
};
using var client = new ClickHouseClient(settings);実行時の CLR 型が For<T> に登録されていない値は、変更されずにそのまま渡されます。ディスパッチは厳密に CLR 型で行われるため、リーダーが実際に生成する型を登録してください (例: JsonReadMode.Binary の JSON カラムには For<JsonObject> を登録します) 。
高度なシナリオ向けのカスタム IReadValueConverter:
ClickHouse 側の type string に基づいてディスパッチする必要がある場合 (たとえば DateTime と DateTime('UTC') を区別したい場合。どちらも同じ CLR 型として扱われます) 、IReadValueConverter を直接実装してください:
public class UtcKindForNoTzDateTimeConverter : IReadValueConverter
{
public object ConvertValue(object value, string columnName, string clickhouseType)
{
if (value is DateTime dt && clickhouseType == "DateTime")
return DateTime.SpecifyKind(dt, DateTimeKind.Utc);
return value;
}
public T ConvertValue<T>(T value, string columnName, string clickhouseType)
{
if (typeof(T) == typeof(DateTime) && value is DateTime dt && clickhouseType == "DateTime")
return (T)(object)DateTime.SpecifyKind(dt, DateTimeKind.Utc);
return value;
}
}コンバーターは、実行時の CLR 型を保持する必要があります。カラムのメタデータ (GetFieldType, GetSchemaTable) はこの仕組みを経由して振り分け直されないため、返される内容と整合している必要があります。
QueryOptions.ReadValueConverter を使うと、クエリごとにコンバーターを設定することもできます。設定した場合は、クライアントレベルのコンバーターよりも優先されます。
ディスパッチ境界:
コンバーターは各カラムごとに、デシリアライズ済みのセル値全体を対象として 1 回だけ呼び出され、複合コンテナーの内部までは再帰的に処理しません。Array(Int32) カラムでは渡される値は int[] であり、Tuple(Int32, String) では ITuple です。
このコンバーターは、ADO.NET の ClickHouseConnection 経由のパスで動作します。設定は、クライアントから作成される接続に引き継がれます。
生データのストリーミング
データリーダーを介さず、特定のフォーマットでクエリ結果を直接ストリーミングするには、ExecuteRawResultAsync を使用します。これは、データをファイルにエクスポートしたり、他のシステムにそのまま渡したりする場合に便利です:
using var result = await client.ExecuteRawResultAsync(
"SELECT * FROM default.my_table LIMIT 100 FORMAT JSONEachRow"
);
await using var stream = await result.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
var json = await reader.ReadToEndAsync();一般的なフォーマット: JSONEachRow, CSV, TSV, Parquet, Native。利用可能なオプションについては、フォーマットのドキュメントを参照してください。
クエリごとの転送圧縮
デフォルトでは、Compression=true (connection-string のデフォルト) が設定されている場合、client は gzip, deflate をネゴシエートし、HTTP client は stream を透過的に展開します。
生のエクスポート (例: Parquet、Arrow、Native) では、接続全体の設定を変更せずに CPU と bandwidth のバランスを取るため、別のコーデック (例: zstd または lz4) をネゴシエートしたいことがあります。QueryOptions.AcceptEncoding と ClickHouseCommand.AcceptEncoding は、単一のリクエストに対して HTTP Accept-Encoding header を設定し、既定で付与されていた値を置き換えるとともに、URL に enable_http_compression=1 を強制的に付与します (これは ClickHouse が Accept-Encoding を受け入れる前に必要とする条件です) 。
using var result = await client.ExecuteRawResultAsync(
"SELECT * FROM events FORMAT Parquet",
options: new QueryOptions { AcceptEncoding = "zstd" });
// Decode yourself or write to a file
await using var body = await result.ReadAsStreamAsync();HttpClient の設定
ドライバーが生成する既定の HttpClient では、AutomaticDecompression = GZip | Deflate が設定されており、これらのアルゴリズムによる圧縮を透過的に展開し、レスポンスから Content-Encoding を取り除きます。通常のクエリではこれは望ましい動作ですが、生の圧縮データを自分で処理したい場合には適していません。
その場合は、AutomaticDecompression = DecompressionMethods.None を設定した HttpClient または HttpClientFactory を ClickHouseClient に渡してください。
エラーのボディ
サーバーが 4xx/5xx で応答し、enable_http_compression=1 が設定されている場合、正常な応答に使用されるものと同じコーデックでエラーのボディも圧縮されます。ドライバーは、BCL に含まれるコーデック (gzip、deflate、br/brotli) であればこれを展開するため、ClickHouseServerException に表示されるメッセージはそのまま読めます。デコードできないコーデック (zstd、lz4、…) については、ドライバーはコーデック名を示し、元のエラーテキストは system.query_log を参照するよう案内するプレースホルダーメッセージを返します。
Raw ストリームの挿入
InsertRawStreamAsync を使用すると、CSV、JSON、Parquet など、または ClickHouse でサポートされている任意のフォーマットのデータを、ファイルストリームまたはメモリストリームから直接挿入できます。
CSVファイルから挿入する:
await using var fileStream = File.OpenRead("data.csv");
using var response = await client.InsertRawStreamAsync(
table: "my_table",
stream: fileStream,
format: "CSV",
columns: ["id", "product", "price"] // Optional: specify columns
);その他の例
さらに実践的な使用例については、GitHubリポジトリ内のexamplesディレクトリを参照してください。
ADO.NET
このライブラリは、ClickHouseConnection、ClickHouseCommand、ClickHouseDataReader を通じて、ADO.NET を完全にサポートしています。この API は、ORM インテグレーション (Dapper、Linq2db) や、標準的な .NET のdatabase 抽象化が必要な場合に不可欠です。
ClickHouseDataSource によるライフタイム管理
適切なライフタイム管理と接続プーリングを確実に行うため、接続は必ず ClickHouseDataSource から作成してください。 DataSource は内部で単一の ClickHouseClient を管理しており、すべての接続はその HTTP 接続プールを共有します。
using ClickHouse.Driver.ADO;
// DataSourceは一度だけ作成する(DIにシングルトンとして登録する)
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default;Password=secret");
// 必要に応じて軽量な接続を作成する
await using var connection = await dataSource.OpenConnectionAsync();
// 接続を使用する
await using var command = connection.CreateCommand("SELECT version()");
var version = await command.ExecuteScalarAsync();依存関係の挿入では:
// Startup.cs または Program.cs 内
services.AddSingleton(sp =>
{
var factory = sp.GetRequiredService<IHttpClientFactory>();
return new ClickHouseDataSource("Host=localhost", factory, "ClickHouse");
});
// サービス内
public class MyService
{
private readonly ClickHouseDataSource _dataSource;
public MyService(ClickHouseDataSource dataSource)
{
_dataSource = dataSource;
}
public async Task DoWorkAsync()
{
await using var connection = await _dataSource.OpenConnectionAsync();
// 接続を使用...
}
}ClickHouseCommand の使用
SQL を実行するコマンドを接続から作成します。
await using var connection = await dataSource.OpenConnectionAsync();
// SQLでコマンドを作成する
await using var command = connection.CreateCommand("SELECT * FROM my_table WHERE id = {id:Int64}");
command.AddParameter("id", 42L);
// 実行して結果を読み取る
await using var reader = await command.ExecuteReaderAsync();
while (reader.Read())
{
Console.WriteLine($"Name: {reader.GetString("name")}");
}コマンド メソッド:
ExecuteNonQueryAsync()- INSERT、UPDATE、DELETE、DDL ステートメントに使用しますExecuteScalarAsync()- 最初の行の最初のカラムを返しますExecuteReaderAsync()- 結果を反復処理するためのClickHouseDataReaderを返します
ClickHouseDataReader の使用
ClickHouseDataReader を使用すると、クエリ結果に型付きでアクセスできます。
await using var reader = await command.ExecuteReaderAsync();
while (reader.Read())
{
// カラムインデックスによるアクセス
var id = reader.GetInt64(0);
var name = reader.GetString(1);
// カラム名によるアクセス
var email = reader.GetString("email");
// 汎用アクセス
var timestamp = reader.GetFieldValue<DateTime>("created_at");
// nullチェック
if (!reader.IsDBNull("optional_field"))
{
var value = reader.GetString("optional_field");
}
}ベストプラクティス
接続の有効期間とプーリング
ClickHouse.Driver は内部で System.Net.Http.HttpClient を使用しています。HttpClient にはエンドポイントごとの接続プールがあります。そのため、次の点に注意してください。
- データベースセッションは、接続プールで管理される HTTP 接続を介して多重化されます。
- HTTP 接続はプールによって自動的に再利用されます。
ClickHouseClientまたはClickHouseConnectionオブジェクトを破棄した後でも、接続が維持される場合があります。
推奨パターン:
| シナリオ | 推奨される方法 |
|---|---|
| 一般的な用途 | シングルトンの ClickHouseClient を使用する |
| ADO.NET / ORMs | ClickHouseDataSource を使用する (同じプールを共有する接続が作成されます) |
| DI 環境 | IHttpClientFactory を使用して、ClickHouseClient または ClickHouseDataSource をシングルトンとして登録する |
DateTime の扱い
-
可能な限り UTC を使用します。 タイムスタンプは
DateTime('UTC')カラムとして保存し、コードではDateTimeKind.Utcを使用します。これにより、タイムゾーンの曖昧さを排除できます。 -
タイムゾーンを明示的に扱うには
DateTimeOffsetを使用します。DateTimeOffsetは常に特定の時点を表し、オフセット情報を含みます。 -
SQL の型ヒントでタイムゾーンを指定します。
Unspecifiedの DateTime 値を持つパラメーターを使用して非 UTC カラムを対象とする場合は、SQL にタイムゾーンを含めます。var parameters = new ClickHouseParameterCollection(); parameters.AddParameter("dt", myDateTime); await client.ExecuteNonQueryAsync( "INSERT INTO table (dt) VALUES ({dt:DateTime('Europe/Amsterdam')})", parameters );
非同期 INSERT
非同期 INSERT では、バッチ化の責任がクライアントからサーバーに移ります。クライアント側でバッチ化する代わりに、サーバーが受信データをバッファに保持し、設定可能なしきい値に基づいてストレージに書き出します。これは、多数のエージェントが小さなペイロードを送信するオブザーバビリティのワークロードのような、高い同時実行性が求められるシナリオで有効です。
CustomSettings または接続文字列で非同期 INSERT を有効にします:
// CustomSettingsを使用する場合
var settings = new ClickHouseClientSettings("Host=localhost");
settings.CustomSettings["async_insert"] = 1;
settings.CustomSettings["wait_for_async_insert"] = 1; // 推奨: フラッシュの完了確認を待機する
// または接続文字列を使用する場合
// "Host=localhost;set_async_insert=1;set_wait_for_async_insert=1"2 つのモード (wait_for_async_insert で制御) :
| Mode | Behavior | Use case |
|---|---|---|
wait_for_async_insert=1 |
データがディスクにフラッシュされた後に insert が完了します。エラーはクライアントに返されます。 | ほとんどのワークロードで推奨 |
wait_for_async_insert=0 |
データがバッファに格納された時点で、insert は即座に完了します。データが永続化される保証はありません。 | データ損失を許容できる場合のみ |
主な設定:
| Setting | Description |
|---|---|
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", // 省略可能 -- 指定しない場合は自動生成されます
};
using var client = new ClickHouseClient(settings);
await client.ExecuteNonQueryAsync("CREATE TEMPORARY TABLE temp_ids (id UInt64)");
await client.ExecuteNonQueryAsync("INSERT INTO temp_ids VALUES (1), (2), (3)");
var reader = await client.ExecuteReaderAsync(
"SELECT * FROM users WHERE id IN (SELECT id FROM temp_ids)"
);ADO.NET を使用する場合 (ORM との互換性のため) :
var settings = new ClickHouseClientSettings
{
Host = "localhost",
UseSession = true,
SessionId = "my-session",
};
var dataSource = new ClickHouseDataSource(settings);
await using var connection = await dataSource.OpenConnectionAsync();
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型 |
|---|---|
| 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 型 | .NET 型 |
|---|---|
| Decimal(P, S) | decimal / ClickHouseDecimal |
| Decimal32(S) | decimal / ClickHouseDecimal |
| Decimal64(S) | decimal / ClickHouseDecimal |
| Decimal128(S) | decimal / ClickHouseDecimal |
| Decimal256(S) | decimal / ClickHouseDecimal |
Boolean 型
| ClickHouse 型 | .NET 型 |
|---|---|
| Bool | bool |
String 型
| ClickHouse 型 | .NET 型 |
|---|---|
| String | string |
| FixedString(N) | string |
日付と時刻の型
| ClickHouse 型 | .NET 型 |
|---|---|
| Date | DateTime |
| Date32 | DateTime |
| DateTime | DateTime |
| DateTime32 | DateTime |
| DateTime64 | DateTime |
| Time | TimeSpan |
| Time64 | TimeSpan |
ClickHouse では、DateTime と DateTime64 の値は内部的に Unix timestamp (epoch からの秒、またはその下位単位) として保存されます。保存は常に UTC ですが、カラムにはタイムゾーンを関連付けることができ、これによって値の表示方法や解釈方法が変わります。
DateTime の値を読み取る際、DateTime.Kind プロパティはカラムのタイムゾーンに基づいて設定されます。
| カラム定義 | 返される DateTime.Kind |
補足 |
|---|---|---|
DateTime('UTC') |
Utc |
UTC タイムゾーンを明示 |
DateTime('Europe/Amsterdam') |
Unspecified |
オフセットが適用される |
DateTime |
Unspecified |
ローカル時刻をそのまま保持 |
UTC 以外のカラムでは、返される DateTime はそのタイムゾーンでのローカル時刻を表します。そのタイムゾーンに対する正しいオフセットを持つ DateTimeOffset を取得するには、ClickHouseDataReader.GetDateTimeOffset() を使用してください。
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('Europe/Amsterdam') ではなく DateTime) については、ドライバーは Kind=Unspecified の DateTime を返します。これにより、タイムゾーンについて何も仮定せず、保存されている時刻の値をそのまま正確に保持できます。
明示的なタイムゾーンを持たないカラムでタイムゾーンを考慮した動作が必要な場合は、次のいずれかを行ってください。
- カラム定義で明示的なタイムゾーンを使用する:
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 データを構造化された形で扱えますが、特殊な ClickHouse 型 (IP アドレス、UUID、高精度の Decimal 値など) は、JSON 構造内では文字列表現に変換されます。 -
String: 生の JSON をstringとして返します。ClickHouse の JSON 表現をそのまま保持できるため、JSON をパースせずにそのまま受け渡したい場合や、デシリアライゼーションを自分で処理したい場合に便利です。
// 設定でStringモードを構成する
var settings = new ClickHouseClientSettings("Host=localhost")
{
JsonReadMode = JsonReadMode.String
};
// または接続文字列で指定する
// "Host=localhost;JsonReadMode=String"その他の型
| ClickHouse 型 | .NET 型 |
|---|---|
| UUID | Guid |
| IPv4 | IPAddress |
| IPv6 | IPAddress |
| Nothing | DBNull |
| Dynamic | 注を参照 |
| Array(T) | T[] (ネストされた Array(Array(T)) はジャグ配列の T[][] として読み取られます。長方形データを多次元 CLR 配列として具体化するには reader.GetFieldValue<T[,]>(ordinal) を使用します) |
| 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[] |
ジオメトリ型
| ClickHouse 型 | .NET 型 |
|---|---|
| Point | Tuple<double, double> |
| Ring | Tuple<double, double>[] |
| LineString | Tuple<double, double>[] |
| Polygon | Ring[] |
| MultiLineString | LineString[] |
| MultiPolygon | Polygon[] |
| Geometry | 注を参照 |
型マッピング: 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 型 | 受け入れ可能な .NET 型 | 備考 |
|---|---|---|
| Float32 | float、および Convert.ToSingle() と互換性のある任意の型 |
|
| Float64 | double、および Convert.ToDouble() と互換性のある任意の型 |
|
| BFloat16 | float、および Convert.ToSingle() と互換性のある任意の型 |
16 ビットの bfloat 形式に切り詰められます |
ブール型
| ClickHouse 型 | 受け入れ可能な .NET 型 | 備考 |
|---|---|---|
| Bool | bool |
文字列型
| ClickHouse 型 | 受け入れ可能な .NET 型 | 注記 |
|---|---|---|
| String | string, byte[], ReadOnlyMemory<byte>, Stream |
バイナリ型はそのまま書き込まれます。ストリームはシーク可能なものと不可能なものがあります |
| FixedString(N) | string, byte[], ReadOnlyMemory<byte>, Stream |
String は UTF-8 でエンコードされたうえでパディングされます。バイナリ型は厳密に N バイトである必要があります |
日付と時刻の型
| ClickHouse 型 | 受け入れ可能な .NET 型 | 注記 |
|---|---|---|
| Date | DateTime, DateTimeOffset, DateOnly, NodaTime 型 |
Unix 日数として UInt16 に変換されます。サポート範囲は [1970-01-01, 2149-06-06] です |
| Date32 | DateTime, DateTimeOffset, DateOnly, NodaTime 型 |
Unix 日数として Int32 に変換されます。サポート範囲は [1900-01-01, 2299-12-31] です |
| DateTime | DateTime, DateTimeOffset, DateOnly, NodaTime 型 |
詳細は以下を参照。サポート範囲は UTC で [1970-01-01, 2106-02-07 06:28:15] です |
| DateTime32 | DateTime, DateTimeOffset, DateOnly, NodaTime 型 |
DateTime と同じ |
| DateTime64 | DateTime, DateTimeOffset, DateOnly, NodaTime 型 |
精度はスケールパラメータに基づきます |
| 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 (時点は保持される)
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.Utc または DateTimeOffset を使用してください。これにより、サーバーのタイムゾーン、クライアントのタイムゾーン、またはカラムのタイムゾーンに関係なく、コードが常に一貫して動作します。
HTTP パラメータと Bulk Copy の違い
Unspecified の DateTime 値を書き込む際、HTTP パラメータのバインドと Bulk Copy には重要な違いがあります。
Bulk Copy は対象カラムのタイムゾーンを認識しており、そのタイムゾーンで Unspecified の値を正しく解釈します。
HTTP Parameters はカラムのタイムゾーンを自動的には認識しません。SQL の型ヒントでそのタイムゾーンを指定する必要があります。
// 正しい例: SQLの型ヒントにタイムゾーンを指定 - 型は自動的に抽出される
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime('Europe/Amsterdam')})";
command.AddParameter("dt", myDateTime);
// 誤った例: タイムゾーンヒントなしの場合、UTCとして解釈される
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime})";
command.AddParameter("dt", myDateTime);
// 文字列値 "2024-01-15 14:30:00" はアムステルダム時間ではなくUTCとして解釈される!DateTime.Kind |
対象カラム | HTTP パラメータ (tz ヒントあり) | HTTP パラメータ (tz ヒントなし) | Bulk Copy |
|---|---|---|---|---|
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 型 | 受け入れ可能な .NET 型 | 注記 |
|---|---|---|
| Json | string、JsonObject、JsonNode、任意のオブジェクト |
動作は JsonWriteMode 設定に依存します |
JSON の書き込み時の動作は、JsonWriteMode 設定で制御されます。
| 入力型 | JsonWriteMode.String (デフォルト) |
JsonWriteMode.Binary |
|---|---|---|
string |
そのまま渡されます | ArgumentException をスローします |
JsonObject |
ToJsonString() でシリアライズされます |
ArgumentException をスローします |
JsonNode |
ToJsonString() でシリアライズされます |
ArgumentException をスローします |
| 登録済み POCO | JsonSerializer.Serialize() でシリアライズされます |
型ヒント付きのバイナリエンコーディング。カスタムパス属性もサポート |
| 未登録の POCO / 匿名オブジェクト | JsonSerializer.Serialize() でシリアライズされます |
ClickHouseJsonSerializationException をスローします |
-
String(デフォルト):string、JsonObject、JsonNode、または任意のオブジェクトを受け付けます。すべての入力はSystem.Text.Json.JsonSerializerでシリアライズされ、サーバー側でパースするために JSON 文字列として送信されます。これは最も柔軟なモードで、型登録なしで動作します。 -
Binary: 登録済みの POCO 型のみを受け付けます。データはクライアント側で、完全な型ヒントのサポート付きで ClickHouse のバイナリ JSON フォーマットに変換されます。使用する前にconnection.RegisterJsonSerializationType<T>()を呼び出す必要があります。このモードでstringまたはJsonNodeの値を書き込むと、ArgumentExceptionがスローされます。
// デフォルトのStringモードはあらゆる入力で動作
await client.InsertBinaryAsync(
"my_table",
new[] { "id", "data" },
new[] { new object[] { 1u, new { name = "test", value = 42 } } }
);
// Binaryモードは明示的なオプトインと型の登録が必要
var settings = new ClickHouseClientSettings("Host=localhost")
{
JsonWriteMode = JsonWriteMode.Binary
};
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<MyPocoType>();型付き JSON カラム
JSON カラムに型ヒント (例: JSON(id UInt64, price Decimal128(2))) がある場合、ドライバーはそれらのヒントを使って、値を型情報を完全に保ったままシリアライズします。これにより、UInt64、Decimal、UUID、DateTime64 など、汎用的な JSON としてシリアライズすると精度が失われる可能性のある型でも、精度を保持できます。
POCO のシリアライゼーション
POCO は、JsonWriteMode に応じて 2 つの方法で JSON カラムに書き込めます。
String mode (default): POCO は System.Text.Json.JsonSerializer によってシリアライズされます。型の登録は不要です。最もシンプルな方法で、匿名オブジェクトでも使用できます。
Binary mode: POCO は、型ヒントを完全にサポートするドライバーのバイナリ JSON フォーマットを使用してシリアライズされます。使用前に connection.RegisterJsonSerializationType<T>() で型を登録する必要があります。このモードでは、属性を使ったカスタムパスマッピングをサポートします。
-
[ClickHouseJsonPath("path")]: プロパティをカスタム JSON パスにマッピングします。ネストされた structure や、プロパティ名が目的の JSON キーと異なる場合に便利です。Binary mode でのみ機能します。 -
[ClickHouseJsonIgnore]: プロパティをシリアライゼーションの対象から除外します。Binary mode でのみ機能します。
CREATE TABLE events (
id UInt32,
data JSON(`user.id` Int64, `user.name` String, Timestamp DateTime64(3))
) ENGINE = MergeTree() ORDER BY idusing ClickHouse.Driver.Json;
public class UserEvent
{
[ClickHouseJsonPath("user.id")]
public long UserId { get; set; }
[ClickHouseJsonPath("user.name")]
public string UserName { get; set; }
public DateTime Timestamp { get; set; }
[ClickHouseJsonIgnore]
public string InternalData { get; set; } // シリアライズされない
}
// バイナリモードの場合: 型を登録してバイナリモードを有効にする
var settings = new ClickHouseClientSettings("Host=localhost") { JsonWriteMode = JsonWriteMode.Binary };
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<UserEvent>();
// POCOを挿入 - カスタムパス属性によりネスト構造を持つJSONにシリアライズされる
await client.InsertBinaryAsync(
"events",
new[] { "id", "data" },
new[] { new object[] { 1u, new UserEvent { UserId = 123, UserName = "Alice", Timestamp = DateTime.UtcNow } } }
);
// 生成されるJSON: {"user": {"id": 123, "name": "Alice"}, "Timestamp": "2024-01-15T..."}プロパティ名とカラムの型ヒントの照合では、大文字と小文字が区別されます。プロパティ UserId は、userid ではなく、UserId と定義されたヒントにのみ一致します。これは、userName と UserName のようなパスを別個のフィールドとして共存させられる ClickHouse の動作と一致しています。
制限事項 (Binary mode のみ) :
- POCO 型は、シリアライズする前に
connection.RegisterJsonSerializationType<T>()を使用してコネクションに登録しておく必要があります。未登録の型をシリアライズしようとすると、ClickHouseJsonSerializationExceptionがスローされます。 - Dictionary および配列/リストのプロパティを正しくシリアライズするには、カラム定義に型ヒントが必要です。ヒントがない場合は、代わりに String mode を使用してください。
- POCO プロパティの null 値は、カラム定義内のパスに
Nullable(T)型ヒントがある場合にのみ書き込まれます。ClickHouse では動的 JSON パス内でNullable型は使用できないため、ヒントのない null プロパティはスキップされます。 ClickHouseJsonPath属性とClickHouseJsonIgnore属性は String mode では無視されます (有効なのは Binary mode のみです) 。
その他の型
| ClickHouse 型 | 受け入れ可能な .NET 型 | 注記 |
|---|---|---|
| UUID | Guid, string |
string は Guid としてパースされます |
| IPv4 | IPAddress, string |
IPv4 である必要があります。string は IPAddress.Parse() でパースされます |
| IPv6 | IPAddress, string |
IPv6 である必要があります。string は IPAddress.Parse() でパースされます |
| Nothing | Any | 何も書き込みません (no-op) |
| Dynamic | — | 未サポート (NotImplementedException をスローします) |
| Array(T) | IList, null |
null は空配列として書き込まれます。ネストされた型 (Array(Array(T)) 以上) では、ジャグ配列 (T[][], List<List<T>>) と長方形の多次元 CLR 配列 (T[,], T[,,], …) のどちらも受け入れられます。CLR のランクは ClickHouse のネストの深さと一致する必要があります。 |
| Tuple(T1, T2, …) | ITuple, IList |
要素数はタプルの要素数と一致する必要があります。要素数が 7 を超える場合は、ValueTuple の注意点 を参照してください。 |
| Map(K, V) | IDictionary |
|
| Nullable(T) | null, DBNull, または T で受け入れ可能な型 |
値の前に null フラグのバイトを書き込みます |
| Enum8 | string, sbyte, 数値型 |
string は enum 辞書で参照されます |
| Enum16 | string, short, 数値型 |
string は enum 辞書で参照されます |
| LowCardinality(T) | T で受け入れ可能な型 | 基になる型に委譲します |
| SimpleAggregateFunction | 基になる型で受け入れ可能な型 | 基になる型に委譲します |
| Nested(…) | タプルの IList |
要素数はフィールド数と一致する必要があります |
| Variant(T1, T2, …) | T1, T2, … のいずれかに一致する値 | 一致する型がない場合は ArgumentException をスローします |
| QBit(T, dim) | IList |
Array に委譲します。次元はメタデータとしてのみ使用されます |
ジオメトリ型
| ClickHouse 型 | 受け入れ可能な .NET 型 | 注記 |
|---|---|---|
| Point | System.Drawing.Point, ITuple, IList (2 要素) |
|
| Ring | Point の IList |
|
| LineString | Point の IList |
|
| Polygon | Ring の IList |
|
| MultiLineString | LineString の IList |
|
| MultiPolygon | Polygon の IList |
|
| Geometry | 上記のいずれかのジオメトリ型 | すべてのジオメトリ型を含む Variant |
書き込みではサポートされていません
| ClickHouse 型 | 備考 |
|---|---|
| Dynamic | NotImplementedException がスローされます |
| AggregateFunction | AggregateFunctionException がスローされます |
ネスト型の扱い
ClickHouse のネスト型 (Nested(...)) は、配列として読み書きできます。
CREATE TABLE test.nested (
id UInt32,
params Nested (param_id UInt8, param_val String)
) ENGINE = Memoryvar 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 client.InsertBinaryAsync(
"test.nested",
new[] { "id", "params.param_id", "params.param_val" },
new[] { row1, row2 }
);ログと診断
ClickHouse の .NET クライアントは、Microsoft.Extensions.Logging の抽象化レイヤーと統合されており、軽量で必要に応じて有効化できるログ機能を提供します。有効にすると、ドライバーは接続ライフサイクル イベント、コマンド実行、トランスポート処理、バルク挿入操作に関する構造化メッセージを出力します。ログ機能は完全に任意であり、ロガーを設定していないアプリケーションも追加のオーバーヘッドなしでそのまま動作し続けます。
クイックスタート
using ClickHouse.Driver;
using Microsoft.Extensions.Logging;
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddConsole()
.SetMinimumLevel(LogLevel.Information);
});
var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
LoggerFactory = loggerFactory
};
using var client = new ClickHouseClient(settings);appsettings.json を使用する
.NET の標準構成機能を使用してログレベルを設定できます。
using ClickHouse.Driver;
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
};
using var client = new ClickHouseClient(settings);インメモリ構成を使用する
コード内で、カテゴリ別にログ出力の詳細度を設定することもできます。
using ClickHouse.Driver;
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
};
using var client = new ClickHouseClient(settings);カテゴリと出力元
ドライバーは専用のカテゴリを使用しているため、コンポーネントごとにログレベルを細かく調整できます。
| カテゴリ | ソース | 主な内容 |
|---|---|---|
ClickHouse.Driver.Connection |
ClickHouseConnection |
接続のライフサイクル、HTTP クライアント ファクトリーの選択、接続のオープン/クローズ、セッション管理。 |
ClickHouse.Driver.Command |
ClickHouseCommand |
クエリ実行の開始/完了、所要時間、クエリ ID、サーバー統計情報、エラーの詳細。 |
ClickHouse.Driver.Transport |
ClickHouseConnection |
低レベルの HTTP ストリーミング リクエスト、圧縮フラグ、レスポンスのステータスコード、トランスポート エラー。 |
ClickHouse.Driver.Client |
ClickHouseClient |
バイナリ insert、クエリ、その他の操作 |
ClickHouse.Driver.NetTrace |
TraceHelper |
ネットワークトレース (デバッグモードが有効な場合のみ) |
例: 接続の問題を診断する
{
"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 クラスを使って手動で有効にします) 。イベントは ClickHouse.Driver.NetTrace カテゴリにログ出力されます。警告: これにより非常に大量の logs が生成され、パフォーマンスに影響します。本番環境でデバッグモードを有効にすることは推奨されません。
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddConsole()
.SetMinimumLevel(LogLevel.Trace); // ネットワークイベントを表示するにはTraceレベルが必要
});
var settings = new ClickHouseClientSettings()
{
LoggerFactory = loggerFactory,
EnableDebugMode = true, // 低レベルのネットワークトレースを有効にする
};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) // ClickHouse ドライバーのスパンを購読する
.AddAspNetCoreInstrumentation()
.AddOtlpExporter()); // または AddJaegerExporter() などコンソールアプリケーション、テスト、または手動セットアップの場合:
using OpenTelemetry;
using OpenTelemetry.Trace;
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)
.AddConsoleExporter()
.Build();スパン属性
各スパンには、標準的なOpenTelemetryのデータベース属性に加え、デバッグに利用できるClickHouse固有のクエリ統計情報が含まれます。
| 属性 | 説明 |
|---|---|
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 を使用して、トレーシングの動作を制御します。
using ClickHouse.Driver.Diagnostic;
// スパンにSQLステートメントを含める(デフォルト: セキュリティのためfalse)
ClickHouseDiagnosticsOptions.IncludeSqlInActivityTags = true;
// 長いSQLステートメントを切り詰める(デフォルト: 1000文字)
ClickHouseDiagnosticsOptions.StatementMaxLength = 500;TLS 設定
HTTPS 経由で ClickHouse に接続する場合、TLS/SSL の挙動はいくつかの方法で設定できます。
カスタム証明書の検証
本番環境でカスタムの証明書検証ロジックが必要な場合は、ServerCertificateCustomValidationCallback ハンドラーを構成した独自の HttpClient を指定します。
using System.Net;
using System.Net.Security;
using ClickHouse.Driver;
var handler = new HttpClientHandler
{
// 圧縮が有効な場合に必要(デフォルトで有効)
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
// 例: 特定の証明書サムプリントを許可する
if (cert?.Thumbprint == "YOUR_EXPECTED_THUMBPRINT")
return true;
// 例: 特定の発行者からの証明書を許可する
if (cert?.Issuer.Contains("YourOrganization") == true)
return true;
// デフォルト: 標準の検証を使用
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 client = new ClickHouseClient(settings);ORM サポート
ORM では ADO.NET API (ClickHouseConnection) が必要です。接続のライフサイクルを適切に管理するため、ClickHouseDataSource から接続を作成してください。
// DataSourceをシングルトンとして登録する
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default");
// ORM用のコネクションを作成する
await using var connection = await dataSource.OpenConnectionAsync();
// コネクションをORMに渡す...Dapper
ClickHouse.Driver は Dapper に対応しています。ドライバーは、Dapper の @parameter 構文を ClickHouse のネイティブな {parameter:Type} 構文に自動変換し、型は .NET の値から推論されます。
適切に connection のライフタイムを管理するには、ClickHouseDataSource を使用します。
var dataSource = new ClickHouseDataSource("Host=localhost");
services.AddSingleton(dataSource); // DIにシングルトンとして登録
using var connection = dataSource.CreateConnection();パラメーターの受け渡し形式
標準的な Dapper のパラメーター指定方法をすべてサポートしています。
匿名オブジェクト:
await connection.ExecuteAsync(
"INSERT INTO users (id, name, balance) VALUES (@Id, @Name, @Balance)",
new { Id = 1, Name = "alice", Balance = 3.14 });POCO クラス:
class InsertParams
{
public int Id { get; set; }
public string Name { get; set; }
public double Balance { get; set; }
}
var param = new InsertParams { Id = 42, Name = "bob", Balance = 99.9 };
await connection.ExecuteAsync(
"INSERT INTO users (id, name, balance) VALUES (@Id, @Name, @Balance)", param);Dictionary:
var parameters = new Dictionary<string, object> { { "Id", 2 } };
var rows = await connection.QueryAsync<User>(
"SELECT id, name FROM users WHERE id = @Id", parameters);DynamicParameters (ディクショナリまたは匿名オブジェクトから) :
var dynParams = new DynamicParameters(new { Id = 1 });
// または: new DynamicParameters(new Dictionary<string, object> { { "Id", 1 } });
var rows = await connection.QueryAsync<User>(
"SELECT id, name FROM users WHERE id = @Id", dynParams);POCO へのクエリ
Dapper は、カラム名に基づいてプロパティにマッピングします (大文字と小文字を区別しません) 。
class User
{
public int Id { get; set; }
public string Name { get; set; }
public double Balance { get; set; }
}
// テーブルから
var users = (await connection.QueryAsync<User>("SELECT id, name, balance FROM users")).ToList();
// リテラルから
var row = (await connection.QueryAsync<User>("SELECT 1 as id, 'hello' as name, 2.5 as balance")).Single();ClickHouseネイティブのパラメーター構文
型を明示的に制御する必要がある場合は、パラメーター値に Dictionary<string, object> を使用し、SQL 内で ClickHouse の {param:Type} 構文を直接使用してください。同じパラメーターに対して @param 構文と {param:Type} 構文を併用しないでください。
var parameters = new Dictionary<string, object> { { "value", 42 } };
var result = await connection.QueryAsync<int>("SELECT {value:Int32}", parameters);WHERE IN
DapperのネイティブなIN展開が機能します:
var rows = await connection.QueryAsync<User>(
"SELECT id, name FROM users WHERE id IN @Ids ORDER BY id",
new { Ids = new[] { 1, 3, 5 } });Dapper はこれを WHERE id IN (@Ids1, @Ids2, @Ids3) に書き換え、ドライバーが展開された各パラメーターをそれぞれ変換します。
Array パラメーターを使った ClickHouse の has() も動作します:
var parameters = new Dictionary<string, object> { { "ids", new[] { 1, 3, 5 } } };
var rows = await connection.QueryAsync<User>(
"SELECT id, name FROM users WHERE has({ids:Array(Int32)}, id) ORDER BY id",
parameters);カスタム型ハンドラー
ITuple、BigInteger、ClickHouseDecimal など、一部の ClickHouse の型では、起動時にハンドラーを登録する必要があります。
// ClickHouseDecimal(Decimal64/128/256カラム用)
SqlMapper.AddTypeHandler(new ClickHouseDecimalHandler());
// BigInteger(Int128/Int256/UInt128/UInt256カラム用)
SqlMapper.AddTypeHandler(new BigIntegerHandler());
// IPAddress(IPv4/IPv6カラム用)
SqlMapper.AddTypeHandler(new IpAddressHandler());型ハンドラーの実装例は、Dapper の例を参照してください。
Dapper.Contrib
GetAll<T>() と Get<T>(id) は動作します。Insert<T>() は動作しません。これは SQL Server の構文 (SCOPE_IDENTITY、[]) を生成するためです。代わりに、ClickHouseClient のネイティブな InsertBinaryAsync メソッドを使用することを推奨します。
[Table("test.users")]
record class UserRecord(int Id, string Name, DateTime Timestamp);
var all = await connection.GetAllAsync<UserRecord>();
var one = await connection.GetAsync<UserRecord>(1);プロパティ名は ClickHouse のカラム名と完全に一致している必要があります (大文字と小文字を区別します) 。
制限事項
| 項目 | ステータス | 詳細 |
|---|---|---|
| 結果としての Tuple | 動作します | SqlMapper.TypeHandler<ITuple> の登録が必要です |
| パラメーターとしての Tuple | 未対応 | Dapper は ITuple/Tuple<> を DbParameter の値としてシリアル化できません |
| パラメーターとしてのネストされた型 | 未対応 | 同じ理由で、Dapper は複雑な型をパラメーター値として受け付けません |
| パラメーターとしての Geo 型 | 未対応 | Point, Ring, Polygon, LineString, MultiLineString, MultiPolygon |
Dapper.Contrib.Insert<T>() |
未対応 | SQL Server 固有の構文を生成します |
Nothing 型 |
未対応 | 意味のある .NET での表現がありません |
Linq2db
このドライバーは、.NET 向けの軽量な ORM/LINQ プロバイダーである linq2db に対応しています。詳細なドキュメントについては、プロジェクトの Web サイトを参照してください。
使用例:
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);テーブルのマッピングは、属性または Fluent API を使用して定義できます。クラス名とプロパティ名がテーブル名およびカラム名と完全に一致している場合は、設定は不要です。
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
ClickHouse 向けの公式 Entity Framework Core プロバイダーです。C# クラスを ClickHouse テーブルにマッピングし、LINQ でクエリを実行し、SaveChanges を通じてデータを insert できます。いずれも使い慣れた EF Core のパターンで行えます。
- NuGet:
ClickHouse.EntityFrameworkCore - Source: GitHub
インストール
dotnet add package ClickHouse.EntityFrameworkCore.NET 10.0 と EF Core 10 が必要です。
クイックスタート
エンティティとDbContextを定義し、LINQ でクエリを実行します。
using Microsoft.EntityFrameworkCore;
public class PageView
{
public long Id { get; set; }
public string Path { get; set; }
public DateOnly Date { get; set; }
public string UserAgent { get; set; }
}
public class AnalyticsContext : DbContext
{
public DbSet<PageView> PageViews { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseClickHouse("Host=localhost;Database=analytics");
}
// クエリ
await using var ctx = new AnalyticsContext();
var topPages = await ctx.PageViews
.Where(v => v.Date >= new DateOnly(2024, 1, 1))
.GroupBy(v => v.Path)
.Select(g => new { Path = g.Key, Views = g.Count() })
.OrderByDescending(x => x.Views)
.Take(10)
.ToListAsync();サポートされる型
| カテゴリ | ClickHouse 型 | CLR 型 |
|---|---|---|
| 整数 | Int8–Int64, UInt8–UInt64 |
sbyte, short, int, long, byte, ushort, uint, ulong |
| 大きな整数 | Int128, Int256, UInt128, UInt256 |
BigInteger |
| 浮動小数点数 | Float32, Float64, BFloat16 |
float, double |
| Decimal | Decimal(P,S), Decimal32(S), Decimal64(S), Decimal128(S) |
decimal または ClickHouseDecimal |
| Bool | Bool |
bool |
| String | String, FixedString(N) |
string |
| 列挙型 | Enum8(...), Enum16(...) |
string または C# enum |
| 日付/時刻 | Date, Date32, DateTime, DateTime64(P, 'TZ') |
DateOnly, DateTime |
| Time | Time, Time64(N) |
TimeSpan |
| UUID | UUID |
Guid |
| ネットワーク | IPv4, IPv6 |
IPAddress |
| Array | Array(T) |
T[], List<T>, IList<T>, ICollection<T>, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T> |
| Map | Map(K, V) |
Dictionary<K,V> |
| Tuple | Tuple(T1, ...) |
Tuple<...> または ValueTuple<...> |
| Variant | Variant(T1, T2, ...) |
object |
| Dynamic | Dynamic |
object |
| JSON | Json |
JsonNode または string |
| 地理空間 | Point, Ring, LineString, Polygon, MultiLineString, MultiPolygon, Geometry |
Tuple<double,double> とその配列。Geometry には object |
| ラッパー型 | Nullable(T), LowCardinality(T) |
自動的にアンラップされます |
Decimal128/Decimal256 カラムの完全な精度が必要な場合は、decimal ではなく ClickHouseDecimal (ClickHouse.Driver.Numerics のもの) を使用してください。.NET の decimal は有効桁数が 28~29 桁に制限されます。
サポートされている LINQ 操作
クエリ: Where, OrderBy, Take, Skip, Select, First, Single, Any, All, Count, Distinct, AsNoTracking
GROUP BY と集計: Count, LongCount, Sum, Average, Min, Max を伴う GroupBy — HAVING (.GroupBy() の後に .Where() を使用) 、1 つのプロジェクション内での複数の集計、集計結果に対する OrderBy を含みます。
JOIN: Join (INNER) 、GroupJoin/SelectMany パターン (LEFT および CROSS) 。LEFT JOIN は、一致しない行に対して実際の null 値を返します (下記の LEFT JOIN の NULL セマンティクス を参照) 。
サブクエリ: 相関 Contains / IN、Any / EXISTS、All、およびプロジェクション内のスカラー サブクエリ。
集合演算: Concat (→ UNION ALL) 、Union (→ UNION DISTINCT) 、Intersect、Except。
インラインのローカルコレクション: インメモリコレクション (int[]、List<T> など) に対する join や Contains は、一連の UNION に変換されます。
文字列メソッド: Contains, StartsWith, EndsWith, IndexOf, Replace, Substring, Trim/TrimStart/TrimEnd, ToLower, ToUpper, Length, IsNullOrEmpty, Concat (および + 演算子) 。
数学関数: 標準の Math および MathF メソッドは、対応する ClickHouse の関数に変換されます — 算術、対数、三角、およびユーティリティ関数。
LEFT JOIN の NULL セマンティクス
このプロバイダーは、JOIN の挙動に関する Entity Framework の想定に合わせるため、接続のたびに set_join_use_nulls=1 を自動的に設定します。
ClickHouse サーバーまたは profile でこの設定の変更が禁止されている場合 (例: readonly=1 の profile) 、次のように無効化してください。
optionsBuilder.UseClickHouse(connectionString, o => o.DisableJoinNullSemantics());オプトアウトが有効な場合、LEFT JOIN は ClickHouse のカラムのデフォルト値を返すため、EF の null ベースのナビゲーション検出は期待どおりに機能しなくなります。== null の代わりに、0 / "" との明示的な比較を使用してください。
データの挿入
SaveChanges では、ドライバーが提供するネイティブの InsertBinaryAsync API を使用します。RowBinary エンコーディングと GZip 圧縮を利用するため、パラメーター化 SQL よりもはるかに効率的です。
await using var ctx = new AnalyticsContext();
ctx.PageViews.Add(new PageView
{
Id = 1,
Path = "/home",
Date = new DateOnly(2024, 6, 15),
UserAgent = "Mozilla/5.0"
});
await ctx.SaveChangesAsync();エンティティは、保存後に他の EF Core プロバイダーと同様、Added から Unchanged に変わります。
バッチサイズ は設定できます (デフォルトは 1000) :
optionsBuilder.UseClickHouse("Host=localhost", o => o.MaxBatchSize(5000));一括挿入
高スループットの読み込みでは、SaveChanges ではなく BulkInsertAsync を使用してください。これは DbContext の拡張メソッドで、EF Core の変更トラッカー、ID 解決、状態管理を完全にバイパスし、RowBinary エンコーディングと GZip 圧縮を使用して、ドライバーの InsertBinaryAsync を直接呼び出します。
そのため、挿入後にエンティティの追跡が不要な大規模データセットの読み込みに適しています。
var events = Enumerable.Range(0, 100_000)
.Select(i => new PageView
{
Id = i,
Path = $"/page/{i}",
Date = DateOnly.FromDateTime(DateTime.Today)
});
long rowsInserted = await ctx.BulkInsertAsync(events);入力には任意の IEnumerable<T> を使用できます。エンティティはすべてをメモリに読み込むことなく順次処理されます。戻り値は挿入された行数です。挿入後もエンティティは DbContext にアタッチされないため、Added → Unchanged の状態遷移は発生しません。
列挙型
ClickHouse Enum8/Enum16 カラムは、string プロパティまたは C# の enum 型にマッピングできます。C# の列挙型を使用する場合、プロバイダーは列挙型とその文字列表現の間で自動的に変換します。
public enum Status { Active, Inactive, Pending }
public class User
{
public long Id { get; set; }
public Status Status { get; set; }
}
// enum値を使ったクエリ
var active = await ctx.Users
.Where(u => u.Status == Status.Active)
.ToListAsync();カスタム型の変換
EF Core の ValueConverter システムを使うと、カスタム型をプロバイダーがすでにサポートしている型にマッピングできます。プロバイダーがカスタム型を直接扱うことはなく、EF Core がその境界で変換を行います。
プロパティ単位の変換:
public class Money
{
public decimal Amount { get; set; }
public string Currency { get; set; }
}
public class Order
{
public long Id { get; set; }
public Money Price { get; set; }
}
// OnModelCreating 内:
modelBuilder.Entity<Order>()
.Property(o => o.Price)
.HasConversion(
m => $"{m.Amount}|{m.Currency}",
s => new Money
{
Amount = decimal.Parse(s.Split('|')[0]),
Currency = s.Split('|')[1]
})
.HasColumnType("String");再利用可能なコンバータークラス:
public class MoneyConverter : ValueConverter<Money, string>
{
public MoneyConverter() : base(
m => $"{m.Amount}|{m.Currency}",
s => Parse(s)) { }
private static Money Parse(string s)
{
var parts = s.Split('|');
return new Money { Amount = decimal.Parse(parts[0]), Currency = parts[1] };
}
}
// 単一のプロパティに適用する場合:
.HasConversion<MoneyConverter>()
// または、規約を使用して型のすべてのプロパティに適用する場合:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<Money>()
.HaveConversion<MoneyConverter>();
}カラム型のアノテーション
string、int、DateTime などのスカラー型では、プロバイダーが ClickHouse の型を自動的に推論します。パラメーター化された型やラッパーについては、ClickHouse の型を明示的に指定する必要があります。
データ アノテーション (属性) を使用する場合:
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
[Table("sensor_readings")]
public class SensorReading
{
public long Id { get; set; }
[Column(TypeName = "Array(String)")]
public string[] Tags { get; set; }
[Column(TypeName = "Map(String, String)")]
public Dictionary<string, string> Metadata { get; set; }
[Column(TypeName = "Nullable(Float64)")]
public double? Value { get; set; }
[Column(TypeName = "Decimal128(18)")]
public decimal HighPrecision { get; set; }
}OnModelCreating で fluent API を使用する方法:
modelBuilder.Entity<SensorReading>(e =>
{
e.ToTable("sensor_readings");
e.Property(x => x.Tags).HasColumnType("Array(String)");
e.Property(x => x.Metadata).HasColumnType("Map(String, String)");
e.Property(x => x.Value).HasColumnType("Nullable(Float64)");
e.Property(x => x.Category).HasColumnType("LowCardinality(String)");
e.Property(x => x.HighPrecision).HasColumnType("Decimal128(18)");
});Array(Nullable(Int32)) や LowCardinality(Nullable(String)) のようなネストされたラッパー型をサポートしています — プロバイダーは Nullable と LowCardinality をどのネストレベルでも自動的にアンラップします。
Variant と Dynamic カラム
ClickHouse の Variant(T1, T2, ...) カラムおよび Dynamic カラムは、.NET では object にマップされます。object は自動的な型推論には汎用的すぎるため、.HasColumnType() でストア型を明示的に指定する必要があります。
public class Event
{
public long Id { get; set; }
public object? Payload { get; set; }
}
// OnModelCreating 内:
entity.Property(e => e.Payload).HasColumnType("Variant(String, UInt64, Array(UInt64))");
// または:
entity.Property(e => e.Payload).HasColumnType("Dynamic");読み取り時には、値は保存されている判別子に対応する .NET 型 (例: string、ulong、ulong[]) に自動的にデシリアライズされます。
JSON カラム
このプロバイダーは ClickHouse の Json カラム型をサポートしており、System.Text.Json.Nodes.JsonNode (プライマリ) または string (自動 ValueConverter 使用時) に対応付けられます。
using System.Text.Json.Nodes;
public class Event
{
public long Id { get; set; }
public JsonNode? Data { get; set; }
}
// OnModelCreating 内:
entity.Property(e => e.Data).HasColumnType("Json");JSON の読み書きは、SaveChanges と BulkInsertAsync の両方で利用できます:
ctx.Events.Add(new Event
{
Id = 1,
Data = JsonNode.Parse("""{"action": "click", "x": 100, "y": 200}""")
});
await ctx.SaveChangesAsync();
var ev = await ctx.Events.Where(e => e.Id == 1).SingleAsync();
string action = ev.Data!["action"]!.GetValue<string>(); // "click"生の JSON 文字列を使いたい場合は、プロパティを string として Json カラム型にマップしてください。プロバイダーが ValueConverter を自動的に適用します。
public class Event
{
public long Id { get; set; }
public string? Data { get; set; } // 生のJSON文字列
}
entity.Property(e => e.Data).HasColumnType("Json");テーブルエンジン
ToTable(name, t => ...) のフルーエント API を使用して、ClickHouse テーブルのエンジンとエンジン固有の句を設定します。エンジンが設定されていない場合、プロバイダーは既定で MergeTree を使用し、ORDER BY はエンティティの主キーに基づいて決定されます。
modelBuilder.Entity<Event>(e =>
{
e.ToTable("events", t => t
.HasMergeTreeEngine()
.WithOrderBy("UserId", "Timestamp")
.WithPartitionBy("toYYYYMM(Timestamp)")
.WithPrimaryKey("UserId")
.WithSettings("index_granularity = 8192"));
});サポートされているエンジンファミリー:
| Engine | Fluent method | Notes |
|---|---|---|
MergeTree |
HasMergeTreeEngine() |
設定がない場合のデフォルト |
ReplacingMergeTree |
HasReplacingMergeTreeEngine("Version", "IsDeleted") or HasReplacingMergeTreeEngine<T>(e => e.Version) |
Version / IsDeleted カラムは省略可能 |
SummingMergeTree |
HasSummingMergeTreeEngine(…) or HasSummingMergeTreeEngine<T>(e => new { … }) |
合計対象のカラムは省略可能 |
AggregatingMergeTree |
HasAggregatingMergeTreeEngine() |
— |
CollapsingMergeTree |
HasCollapsingMergeTreeEngine("Sign") or HasCollapsingMergeTreeEngine<T>(e => e.Sign) |
Sign カラムは Int8 である必要があります |
VersionedCollapsingMergeTree |
HasVersionedCollapsingMergeTreeEngine("Sign", "Version") or <T>(e => e.Sign, e => e.Version) |
— |
GraphiteMergeTree |
HasGraphiteMergeTreeEngine("config_section") |
— |
Log, TinyLog, StripeLog, Memory |
HasLogEngine(), HasTinyLogEngine(), HasStripeLogEngine(), HasMemoryEngine() |
ORDER BY / PARTITION BY は使用不可 |
エンジン句: WithOrderBy, WithPartitionBy, WithPrimaryKey, WithSampleBy, WithTtl, WithSettings。いずれも HasXxxEngine() が返すエンジンビルダーに対して指定します。
カラムレベルの機能: HasCodec, HasTtl, HasComment, HasDefault — いずれも移行の対象になります。
データスキッピング索引 — HasIndex(...).HasSkippingIndexType(...) で指定します:
modelBuilder.Entity<Event>()
.HasIndex(e => e.UserId)
.HasSkippingIndexType("minmax")
.HasGranularity(4);
// パラメータ付きインデックス(例: bloom_filter、tokenbf_v1):
modelBuilder.Entity<Event>()
.HasIndex(e => e.Tag)
.HasSkippingIndexType("bloom_filter")
.HasSkippingIndexParams("0.01")
.HasGranularity(1);標準の (スキップしない) 索引は、ClickHouse に相当するものがないため、黙って無視されます。一意索引については、ClickHouse では一意性が保証されないため、例外がスローされます。
移行
標準的な EF Core の移行ワークフロー:
dotnet ef migrations add InitialCreate
dotnet ef database updateサポートされている操作:
| 操作 | 生成内容 |
|---|---|
CREATE TABLE |
engine 句、ORDER BY、PARTITION BY、SETTINGS、カラムの codec/TTL/コメント/デフォルト値を含む |
ALTER TABLE ADD COLUMN |
— |
ALTER TABLE DROP COLUMN |
— |
ALTER TABLE MODIFY COLUMN |
型変更に加え、属性の追加/削除 (CODEC、TTL、COMMENT、DEFAULT) に対応 |
ALTER TABLE RENAME COLUMN |
— |
RENAME TABLE |
— |
ALTER TABLE ADD INDEX / DROP INDEX |
データスキッピング索引のみ |
CREATE DATABASE / DROP DATABASE |
EnsureCreated / EnsureDeleted および移行を介して実行 |
移行の制限事項
| 機能 | 理由 |
|---|---|
| 外部キー | ClickHouse は外部キーを強制しません。移行では AddForeignKey は拒否され、モデル バリデーターはモデルのビルド時に警告を出します。 |
| 一意制約 / 一意索引 | ClickHouse は一意性を保証しません。一意索引は移行時に例外をスローします。 |
サーバー生成値 (auto-increment / IDENTITY) |
ClickHouse に相当する機能はありません。 |
Nested(…) カラム |
マップされた CLR 型としてはまだサポートされていません。 |
JSON としての所有エンティティ (.ToJson()) |
所有エンティティに対する構造的な JSON マッピングはまだ実装されていません。代わりに、Json カラムで JsonNode / string を使用してください (JSON カラム を参照) 。 |
移行以外にも、このプロバイダーはまだ以下をサポートしていません。
UPDATE/DELETE- トランザクション:
BeginTransactionは no-op です。ClickHouse は ACID トランザクションをサポートしていません。 - JSON パス クエリの変換: LINQ の
entity.Data["key"]は、ClickHouse のdata.keySQL 構文に変換されません。JSON 以外のカラムでフィルタリングし、JSON はメモリ上で確認してください。
制限事項
要素が 8 個以上で、最後の位置にネストしたタプルがある Tuple
要素数が 7 を超える C# の ValueTuple 型では、コンパイラ生成のネスト方式が使われます。つまり、8 番目のジェネリック引数 (TRest) は、それ自体が残りの要素を保持する ValueTuple になります。たとえば、(int, int, int, int, int, int, int, string, string) は ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>> にコンパイルされます。
このため、ClickHouse のカラムが 8 要素のタプルで、最後の要素自体もタプルである場合に曖昧さが生じます。たとえば、Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Tuple(String, String)) のようなケースです。ドライバーは次の 2 つを区別できません。
- フラットな 9 要素タプル (コンパイラ生成の TRest ネスト)
- 最後の要素がネストした
Tuple(String, String)である 8 要素タプル
どちらも同じ .NET 型 ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>> になります。
ドライバーは 8 番目の引数を TRest として扱い (つまりフラット化し) 、そのため最後にネストしたタプルを持つ 8 要素のケースは誤ってシリアライズされます。
これは、どちらも 7 要素超で TRest ネストを使うため、System.Tuple と ValueTuple の両方に影響します。要素が 7 個以下のタプル、または最後の要素自体がタプルではないタプルは影響を受けません。
回避策: ドライバーが TRest ネストと区別できるように、内側のタプルをさらに 1 層ラップします。
// Instead of this (ambiguous — is it 8 elements or 9 flat?):
Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create("a", "b"))
// Do this (unambiguous — inner tuple is wrapped):
Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(Tuple.Create("a", "b")))AggregateFunction カラム
AggregateFunction(...) 型のカラムは、直接クエリしたり、挿入したりすることはできません。
挿入するには:
INSERT INTO t VALUES (uniqState(1));取得するには:
SELECT uniqMerge(c) FROM t;