Skip to main content
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 接続プールを共有しており、同じアプリケーション内で併用できます。

移行ガイド

  1. .csproj ファイルで、パッケージ名を新しい ClickHouse.Driver に変更し、NuGet の最新バージョン を指定します。
  2. コードベース内の ClickHouse.Client への参照をすべて ClickHouse.Driver に更新します。

サポート対象の .NET バージョン

ClickHouse.Driver は、以下の .NET バージョンをサポートしています。
  • .NET 6.0
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

サポートされている ClickHouse バージョン

このクライアントは、直近3つのリリースと直近2つのLTSリリースを公式にサポートしています。

インストール

NuGet からパッケージをインストールします。
または、NuGet パッケージ マネージャーを使用します。

クイックスタート

設定

ClickHouse への接続を設定する方法は 2 つあります。
  • 接続文字列: ホスト、認証情報、その他の接続オプションを指定する、セミコロン区切りのキーと値のペアです。
  • ClickHouseClientSettings object: 設定ファイルから読み込むことも、コード内で設定することもできる、厳密に型付けされた設定オブジェクトです。
以下に、すべての設定項目、そのデフォルト値、およびそれぞれの動作への影響を一覧で示します。

接続設定

データフォーマットとシリアライゼーション

セッション管理

UseSession フラグを有効にすると、サーバー側セッションの状態が保持され、SET ステートメントや一時テーブルを利用できるようになります。セッションは 60 秒間非アクティブな状態が続くとリセットされます (デフォルトのタイムアウト) 。セッションの有効期間は、ClickHouse ステートメントまたはサーバー設定でセッション設定を指定することで延長できます。通常、ClickHouseConnection クラスでは並列動作が可能で、複数のスレッドからクエリを同時実行できます。ただし、UseSession フラグを有効にすると、1 つの接続で同時に実行できるアクティブなクエリは常に 1 つに制限されます (これはサーバー側の制約です) 。

セキュリティ

HTTP クライアントの構成

ログとデバッグ

カスタム設定とロール

接続文字列でカスタム設定を指定する場合は、set_ プレフィックスを使用します。たとえば "set_max_threads=4" のように指定します。ClickHouseClientSettings オブジェクトを使用する場合は、set_ プレフィックスは使用しません。使用可能な設定の一覧については、こちらを参照してください。

接続文字列の例

基本接続

カスタムのClickHouse設定を使用する場合


QueryOptions

QueryOptions を使用すると、クライアント レベルの設定をクエリごとに上書きできます。すべてのプロパティは省略可能で、指定した場合にのみクライアントのデフォルト値を上書きします。 例:

InsertOptions

InsertOptions は、InsertBinaryAsync による一括挿入操作に固有の設定を追加して、QueryOptions を拡張したものです。 QueryOptions のすべてのプロパティは、InsertOptions でも利用できます。 例:

スキーマプローブクエリのスキップ

デフォルトでは、InsertBinaryAsync は各 insert の前に SELECT ... WHERE 1=0 クエリを送信し、カラムの型を特定します。高スループットが求められる場合は、2 つの方法でこのオーバーヘッドをなくせます。 オプション 1: カラム型を明示的に指定する コンパイル時点でテーブルのスキーマがわかっている場合は、ColumnTypes で直接指定します。これにより、スキーマプローブクエリは一切送信されません。
オプション 2: スキーマをキャッシュする 同じテーブルに繰り返し insert する場合は、UseSchemaCache = true を設定すると、スキーマのクエリは最初の 1 回だけで済み、同じ ClickHouseClient インスタンスでの以降の insert に再利用されます:
  • ColumnTypes は UseSchemaCache より優先されます。両方が設定されている場合は、明示的に指定した型が使用されます。
  • スキーマ cache では ALTER TABLE による変更は検出されません。テーブルのスキーマを変更した場合は、新しい ClickHouseClient を作成するか、そのテーブルでは UseSchemaCache を使用しないでください。
  • cache は ClickHouseClient インスタンス単位で管理され、キーは (database, table) です。同じテーブル上の異なるカラムのサブセットでは、1 つのキャッシュ済みスキーマが共有されます。

ClickHouseClient

ClickHouseClient は、ClickHouse と連携するための推奨 API です。スレッドセーフで、シングルトンとして利用することを前提に設計されており、HTTP 接続プーリングも内部で管理します。

クライアントの作成

接続文字列または ClickHouseClientSettings オブジェクトを使用して、ClickHouseClient を作成します。利用可能なオプションについては、設定 セクションを参照してください。 ClickHouse Cloud サービスの詳細は、ClickHouse Cloud コンソールで確認できます。 サービスを選択し、Connect をクリックします。 C# を選択します。接続情報が下に表示されます。 セルフマネージドの ClickHouse を使用している場合、接続情報は ClickHouse 管理者が設定します。 接続文字列を使用する場合:
または、ClickHouseClientSettings を使用します。
依存関係の注入のシナリオでは、IHttpClientFactory を使用します。
ClickHouseClient は長期間の利用を前提としており、アプリケーション全体で共有して使うように設計されています。一度だけ作成し (通常はシングルトンとして) 、すべてのデータベース操作で再利用してください。クライアントは内部で HTTP 接続プーリングを管理します。

クエリの実行

結果を返さないステートメントには ExecuteNonQueryAsync を使用します:
単一の値を取得するには、ExecuteScalarAsync を使用します:

データの挿入

パラメーター化された挿入

ExecuteNonQueryAsync を使用して、パラメーター化クエリでデータを挿入します。パラメーターの型は、SQL 内で {name:Type} 構文を使って指定する必要があります。

一括挿入

大量の行を効率よく挿入するには、InsertBinaryAsync を使用します。これは ClickHouse のネイティブな行バイナリ形式でデータをストリーミングし、バッチの並列アップロードをサポートするとともに、パラメーター化クエリで発生することがある「URL が長すぎる」エラーを回避します。
大規模なデータセットでは、InsertOptions を使用してバッチ処理と並列度を設定します:
  • クライアントは挿入前に SELECT * FROM <table> WHERE 1=0 を実行して、テーブル構造を自動的に取得します。指定する値は、対象カラムの型と一致している必要があります。このクエリを省略するには、InsertOptions.ColumnTypes または InsertOptions.UseSchemaCache を使用してください。
  • MaxDegreeOfParallelism > 1 の場合、バッチは並列にアップロードされます。セッションは並列挿入に対応していないため、セッションを無効にするか、MaxDegreeOfParallelism = 1 に設定してください。
  • 指定していないカラムに対してサーバーが DEFAULT 値を適用するようにするには、InsertOptions.Format で RowBinaryFormat.RowBinaryWithDefaults を使用してください。

POCO の挿入

object[] 配列を組み立てる代わりに、厳密に型付けされた POCO オブジェクトを直接 insert できます。型を一度登録したら、あとは IEnumerable<T> を渡します:
既定では、公開されているすべての読み取り可能なプロパティは、厳密な大文字と小文字の区別を伴う名前一致によりカラムにマッピングされます。属性を使用して、このマッピングをカスタマイズできます。
マップされたすべてのプロパティで Type が明示的に指定されている場合、スキーマプローブクエリは完全にスキップされます。一部のプロパティにしか明示的な型が指定されていない場合、ドライバーはカラム一式に対するスキーマプローブにフォールバックします。 InsertBinaryAsync<T> は、object[] オーバーロードと同じ InsertOptions (バッチ化、並列度、スキーマキャッシュ) をサポートします。
object[] オーバーロードとは異なり、InsertBinaryAsync<T> では明示的なカラムリストを指定できません。カラムは、登録された型のマップ済みプロパティに基づいて決定されます。挿入するカラムを制御するには、[ClickHouseNotMapped] を使ってプロパティを除外するか、[ClickHouseColumn(Name = "...")] を使って名前を変更します。InsertOptions で ColumnTypes が設定されている場合は、POCO 属性よりそちらが優先されます。

スキーマ進化

型の登録後にターゲットテーブルへカラムが追加されても、POCO による挿入はそのまま問題なく動作します。ドライバーが挿入するのは POCO にマッピングされたカラムだけなので、DEFAULT (またはその他のデフォルト式) を持つ新しいカラムはサーバー側で自動的に補完されます。コードを変更したり、再登録したりする必要はありません。

INSERT クエリの配置

バイナリ挿入では、INSERT INTO ... FORMAT ... ステートメントが行データの前、リクエストボディの先頭行として書き込まれます。ボディはデフォルトで圧縮されるため、URL のみを検査するルーティングやログからはこのステートメントが見えません。InsertOptions.QueryPlacement に InsertQueryPlacement.Url を設定すると、ステートメントは代わりに query URL パラメータとして送信され、ボディには行データのみが残ります:
プロキシ、ロードバランサー、ゲートウェイが query パラメーターに基づいてルーティングや検査を行う場合、あるいはステートメントをアクセスログやオブザーバビリティツールに記録したい場合に使用してください。ステートメントが URL の長さに算入されるため、この動作はオプトイン方式となっています。実効的な上限は、.NET ランタイム、中間装置、サーバーが課す制限のうち最も低いものになります。.NET 6 から .NET 9 では、System.Uri がエンコード後のリクエスト URI 全体を 65,519 文字に制限します。この上限を超えると、ドライバーは InvalidOperationException をスローし、InsertQueryPlacement.Body に戻すよう促します。ClickHouse の http_max_uri_size はデフォルトで 1 MiB ですが、中間装置がこれより低い制限を課す場合があります。ボディモードでは、ステートメントと行にこのような URL 長の制限はありません。ただし、他のリクエストオプションは引き続き URL に現れる可能性があります。 この設定は Compressor とは独立しています。ボディはどちらのモードでも同じ方法でエンコードされます。

データの読み取り

SELECT クエリの実行には ExecuteReaderAsync を使用します。返される ClickHouseDataReader では、GetInt64()、GetString()、GetFieldValue<T>() などのメソッドを使って、結果カラムに型付きでアクセスできます。 次の行に進むには Read() を呼び出します。これ以上行がない場合は false を返します。カラムには、インデックス (0 始まり) またはカラム名でアクセスできます。

POCO の読み取り

カラムをインデックスや名前で読み取る代わりに、クエリ結果を独自のクラスへ直接ストリームできます。型をクライアントに一度登録すれば、QueryAsync<T> を使用できます。
RegisterPocoType<T>() は、insert と read の両方のマッピングを設定し、両方を事前に検証します。RegisterBinaryInsertType<T>() に変更はなく、backwards compatibility のため引き続き insert 専用です。 登録する型は、次の条件を満たしている必要があります。
  • public の引数なしコンストラクター
  • public で init ではない setter を持つ public プロパティが少なくとも 1 つあること。required プロパティもサポートされます。
カラムのマッチングでは大文字と小文字が区別されます。結果に存在しないカラムについては、対応するプロパティはデフォルト値のままとなり、余分な結果カラムは無視されます。 ドライバーは値の拡大変換や縮小変換を行いません。以下に挙げる代替表現を除き、カラムのフレームワーク型はプロパティの型に代入可能である必要があり、一致しない場合は InvalidOperationException がスローされます。したがって、object 型のプロパティは任意のカラムを受け入れます。 QueryAsync<T> は、以下の各カラムを対応するプロパティへ直接読み込みます: いずれの行でも、カラムが Nullable(...) であるかどうかにかかわらず、そのプロパティ型の nullable 形式(long?、DateOnly? など)を使用できます。Nullable(T) カラムに対して null 非許容の値型プロパティを指定した場合、登録時には受け付けられますが、NULL が到着した時点で例外がスローされます。 LowCardinality(T)、SimpleAggregateFunction(f, T)、Object(T) といったラッパーは、T とまったく同じようにマッピングされます。 複合型のカラムもサポートされており、読み取り時の型リファレンスに記載されたフレームワーク型が使用されます。すなわち、Array(T) は T[]、Tuple(...) は System.Tuple<...>、Nested(...) は Tuple<...>[]、JSON は JsonObject(JsonReadMode=String では string)、Variant/Dynamic は object となります。 Map(K, V) カラムは特別なケースです。List<KeyValuePair<K, V>> または KeyValuePair<K, V>[] のプロパティはボックス化を伴わない経路で読み取られ、いずれの MapReadMode でもワイヤ上の順序と重複するキーをそのまま保持します。Dictionary<K, V> プロパティはデフォルトモードでのみ利用できます。キーと値の型は厳密に一致している必要があるため、Map(String, Nullable(Int32)) には KeyValuePair<string, int?> が必要です。 1 つのカラムに複数のプロパティ型が用意されている場合(DateTime カラムに対する DateTime、DateTimeOffset、DateOnly、String カラムに対する string または byte[] など)、宣言したプロパティ型によって表現が決まります。これらの代替表現は POCO 経路に属するものであるため、QueryAsync<T> では利用できますが、MapTo<T> では利用できません。 リーダーを手動で順に処理する場合は、ClickHouseDataReader.MapTo<T>() を使用して、リーダーを進めずに現在の行を登録済みの POCO にマテリアライズします。
リーダーのループを自分で制御する必要がある場合、たとえば生のカラムアクセスと POCO のマテリアライズを組み合わせたい場合には、MapTo<T> を使用します。これはリーダーのボックス化された値を通じて行を読み取るため、上記の代替プロパティ型には対応しておらず、QueryAsync<T> よりも多くのアロケーションが発生します。行だけが必要な場合は QueryAsync<T> を使用してください。具体的な数値については マテリアライズ方式の選択 を参照してください。 クライアントレベルまたはクエリ単位の読み取り値コンバーターは両方のパスに適用され、 ボックス化なしの読み取りを無効化することはありません。ドライバーは、各カラムをそのカラムの読み取り方法に対応する オーバーロードで変換します。すなわち、ボックス化なしのカラムには型付きの ConvertValue<T> を、 複合型のカラムにはボックス化された ConvertValue を使用します。この2つのオーバーロードは一貫性を保って 実装してください。そうしないと、同じカラムであってもパスによって結果が異なってしまいます。 LoggerFactory が設定されている場合、RegisterPocoType<T>() と RegisterBinaryInsertType<T>() は、どのプロパティがどのカラムにマッピングされたか、またどのプロパティがなぜスキップされたのかを示す Debug レベルのログ (カテゴリ ClickHouse.Driver.Client) を出力します。詳しくは、ロギングと診断情報を参照してください。

SQLパラメータ

ClickHouse では、SQLクエリのクエリパラメータの標準的なフォーマットは {parameter_name:DataType} です。 例:
SQL の’bind’ パラメータは HTTP URI のクエリパラメータとして渡されるため、数が多すぎると “URL が長すぎる” 例外が発生することがあります。この制限を回避してデータを一括挿入するには、InsertBinaryAsync を使用してください。

ADO 形式の @name プレースホルダー

ドライバーは、Dapper などの ORM が出力する @name プレースホルダーも受け付けます。これはクライアント側の利便性のための機能で、リクエスト送信前に各プレースホルダーが {name:ResolvedType} へ書き換えられるため、サーバー側に @ が渡ることはありません。型の選択方法については 型解決 を参照してください。可能な限り、明示的な {name:Type} 形式を使用してください。 対応するパラメーターが存在しない @name はそのまま残され、サーバー側で拒否されます。マッチングでは大文字と小文字が区別されるため、@ID は id という名前のパラメーターにはバインドされません。
この書き換えを無効にするには、ドライバーの初回使用前に ClickHouse.Driver.DisableReplacingParameters AppContext スイッチを設定してください。停止するのはテキストの書き換えのみで、パラメーター自体は引き続き送信されるため、ネイティブな {name:Type} 構文で記述されたクエリはそのまま動作します。

Identifier パラメーター

Identifier パラメーター型を使用すると、引用符付きの文字列リテラルの代わりに、データベース名、テーブル名、またはカラム名を安全にバインドできます。SQL では {name:Identifier} 構文を使用するか、ClickHouseDbParameter.ClickHouseType = "Identifier" を設定して使用します。
値はそのまま送信され、サーバーがそれをクォートなしのSQL識別子として置き換え、サーバー側のバッククォートによる引用とエスケープを適用します。特殊文字 (バッククォートを含む) を含む識別子も、安全にラウンドトリップできます。

クエリ ID

すべてのクエリには一意の query_id が割り当てられます。これは、system.query_log テーブルからデータを取得したり、長時間実行中のクエリをキャンセルしたりする際に使用できます。QueryOptions でカスタムのクエリ ID を指定することもできます。
カスタムの QueryId を指定する場合は、呼び出しごとに必ず一意になるようにしてください。ランダムな GUID を使うのが適切です。

カスタム パラメータ型マッピング

@ 形式のパラメータ (例: WHERE id = @id) を使用すると、ドライバーは .NET の値型から ClickHouse の型を自動的に推論します。たとえば、int は Int32 にマッピングされます。
推論される DateTime パラメータの挙動SQL に {name:Type} ヒントがなく、ClickHouseType も設定されていない @ 形式のパラメータでは、時点を表す値は単なる DateTime ではなく DateTime('UTC') として推論されます。Kind が Utc または Local の DateTime と、すべての DateTimeOffset の値は DateTime('UTC') として送信されるため、どのサーバータイムゾーンでも同じ時点が保持されます。明示的なヒント ({name:DateTime}) は推論より優先され、クエリを構築する推奨方法です。
これらの既定の対応を上書きするには、ClickHouseClientSettings で ParameterTypeResolver を設定します。これは、個々のパラメータごとに ClickHouseType を設定しなくても、すべての DateTime パラメータでミリ秒精度の DateTime64(3) を使いたい場合や、すべての decimal で特定の小数点以下桁数を使いたい場合に便利です。 シンプルな型マッピングに DictionaryParameterTypeResolver を使用する:
高度な用途向けのカスタム IParameterTypeResolver: 値や名前に基づいて解決する場合は、IParameterTypeResolver インターフェイスを直接実装します。既定の推論に委ねるには、null を返します。
単一のクエリに対しては、QueryOptions.ParameterTypeResolver を介してリゾルバを設定することもできます。設定した場合、クライアントレベルのリゾルバより優先されます。 型解決の優先順位: リゾルバは優先順位チェーンの一要素です。優先度の高いものから低いものの順に示すと、次のとおりです。
  1. パラメータに明示的に設定された ClickHouseType
  2. クエリ内の {name:Type} 構文による SQL の型ヒント
  3. IParameterTypeResolver (QueryOptions.ParameterTypeResolver を使用し、未設定の場合は ClickHouseClientSettings.ParameterTypeResolver にフォールバック)
  4. 組み込みの型推論 (TypeConverter.ToClickHouseType)
このリゾルバは、ADO.NET の ClickHouseConnection パスでも機能します。設定は、クライアントから作成された接続に引き継がれます。

カスタム パラメータ値のフォーマット

IParameterFormatter は、パラメータ値をどのようにシリアライズするかを決定するフックです。組み込みのフォーマット (例: DateTime の精度、小数のカルチャ、文字列のエスケープ、数値表現) が、スキーマや後続のツールの想定と一致しない場合に使用します。 パラメータ化されたすべてのクエリにフォーマッタを適用するには、ClickHouseClientSettings で ParameterFormatter を設定します。このフォーマッタは、値、解決された ClickHouse の型名、パラメータ名を受け取り、サーバーに送信される文字列表現を返します。既定のフォーマッタに処理を委ねるには、null を返します。 単純な CLR 型ごとのフォーマットには DictionaryParameterFormatter を使用します:
高度なユースケース向けのカスタム IParameterFormatter:
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 は不要です。 フォーマッタの優先順位:
  1. IParameterFormatter (QueryOptions.ParameterFormatter を使用し、未設定の場合は ClickHouseClientSettings.ParameterFormatter にフォールバック) 。これが non-null を返した場合は、その値が使用されます。
  2. HttpParameterFormatter に組み込まれている type 固有のフォーマット。
このフォーマッタは null または DBNull の値には適用されません。これらは常に ClickHouse の null センチネル (\N) として serialize されます。

カスタム読み取り値変換

IReadValueConverter を使用すると、CLR 型を変更せずに、データリーダーが返す値をデシリアライゼーション後に変換できます。一般的な用途としては、タイムゾーンを持たない DateTime カラムに対して DateTime.Kind = Utc を設定すること、文字列のトリミングや正規化を行うこと、あるいは JSON カラムがアプリケーションコードに渡される前に後処理することなどがあります。 すべての読み取りに対してコンバーターを適用するには、ClickHouseClientSettings で ReadValueConverter を設定します。コンバーターは、ボックス化された (GetValue) パスとジェネリック (GetFieldValue<T>) パスの両方で、各カラムの各行ごとに 1 回呼び出されます。コンバーターが設定されていない場合、オーバーヘッドはゼロで、リーダーは値をそのまま返します。 単純な CLR 型単位の変換に DictionaryReadValueConverter を使用する:
実行時の CLR 型が For<T> に登録されていない値は、変更されずにそのまま渡されます。ディスパッチは厳密に CLR 型で行われるため、リーダーが実際に生成する型を登録してください (例: JsonReadMode.Binary の JSON カラムには For<JsonObject> を登録します) 。 高度なシナリオ向けのカスタム IReadValueConverter: ClickHouse 側の type string に基づいてディスパッチする必要がある場合 (たとえば DateTime と DateTime('UTC') を区別したい場合。どちらも同じ CLR 型として扱われます) 、IReadValueConverter を直接実装してください:
コンバーターは、実行時の CLR 型を保持する必要があります。カラムのメタデータ (GetFieldType, GetSchemaTable) はこの仕組みを経由して振り分け直されないため、返される内容と整合している必要があります。 QueryOptions.ReadValueConverter を使うと、クエリごとにコンバーターを設定することもできます。設定した場合は、クライアントレベルのコンバーターよりも優先されます。 ディスパッチ境界: コンバーターは各カラムごとに、デシリアライズ済みのセル値全体を対象として 1 回だけ呼び出され、複合コンテナーの内部までは再帰的に処理しません。Array(Int32) カラムでは渡される値は int[] であり、Tuple(Int32, String) では ITuple です。 どちらのオーバーロードが実行されるか: ドライバーがどちらを呼び出すかは、呼び出し元がカラムをどのように読み取ったかによって決まるため、両方のオーバーロードは一致している必要があります:
  • ConvertValue<T> — 型付きアクセサである GetByte, GetSByte, GetInt16/32/64, GetUInt16/32/64, GetFloat, GetDouble, GetGuid, GetDateTime, GetIPAddress, GetBigInteger および GetFieldValue<T>、さらに POCO 読み取りパスにおけるボックス化を伴わないすべてのカラム。
  • ConvertValue (ボックス化) — GetValue, GetValues, インデクサー, GetChar, GetTuple、および GetBoolean, GetDecimal, GetString における型強制を伴うパス。
IsDBNull はコンバーターをまったく実行しません。null フラグを直接読み取るため、コンバーターによって 値が null と見なされるかどうかが変わることはありません。TryGetEnumOrdinal も同様にコンバーターを 迂回します — enum の序数の読み取りを参照してください。 このコンバーターは、ADO.NET の ClickHouseConnection 経由のパスで動作します。設定は、クライアントから作成される接続に引き継がれます。

生データのストリーミング

データリーダーを介さず、特定のフォーマットでクエリ結果を直接ストリーミングするには、ExecuteRawResultAsync を使用します。これは、データをファイルにエクスポートしたり、他のシステムにそのまま渡したりする場合に便利です:
一般的なフォーマット: JSONEachRow, CSV, TSV, Parquet, Native。利用可能なオプションについては、フォーマットのドキュメントを参照してください。

クエリごとの転送圧縮

デフォルトでは、Compression=true (connection-string のデフォルト) が設定されている場合、client は zstd, lz4, gzip, deflate をネゴシエートし、stream を自身で透過的にデコードします。 生のエクスポート (例: Parquet、Arrow、Native) では、接続全体の設定を変更せずに CPU と bandwidth のバランスを取るため、別のコーデック (例: zstd または lz4) をネゴシエートしたいことがあります。QueryOptions.AcceptEncoding と ClickHouseCommand.AcceptEncoding は、単一のリクエストに対して HTTP Accept-Encoding header を設定し、既定で付与されていた値を置き換えるとともに、URL に enable_http_compression=1 を強制的に付与します (これは ClickHouse が Accept-Encoding を受け入れる前に必要とする条件です) 。

HttpClient configuration

設定は不要です。ドライバーが構築する HttpClient は AutomaticDecompression を DecompressionMethods.None のままにし、レスポンスのデコードはドライバー自身が行います。そのため Content-Encoding が知らないうちに取り除かれることはなく、生のボディがサーバーの送信したままの形で手元に届きます。
独自の HttpClient を渡す場合も、AutomaticDecompression は無効のままにしてください。これはレスポンス側だけの設定ではありません。送信時に、ハンドラーは自身のマスクに含まれるアルゴリズムのうち、送出される Accept-Encoding に含まれていないものをすべて追加します。そのため GZip | Deflate を持つハンドラーは、明示的に指定した AcceptEncoding = "lz4" を lz4, gzip, deflate に、明示的な "identity" を identity, gzip, deflate に、実際の通信上では書き換えてしまいます。さらに ClickHouse はこのヘッダーを独自の固定的なコーデック優先順位で解決する (順序や q 値は無視する) ため、まったく要求していないコーデックで応答することがあり、それをハンドラーがデコードして取り除いてしまうため、何が起きたのかを知ることすらできません。マスクを無効にしておけば、提示する内容は指定したとおりに保たれます。
AcceptEncoding でドライバーがデコードできないコーデック (snappy) を要求した場合、安全なのは ExecuteRawResultAsync のみです。ExecuteReaderAsync、ExecuteScalarAsync、ExecuteNonQueryAsync は、そのコーデック名を含む NotSupportedException で失敗します (以前は圧縮バイト数をそのまま結果フォーマットとして parse し、意味をなさないデータを生成していました) 。

エラーのボディ

サーバーが 4xx/5xx で応答し、enable_http_compression=1 が設定されている場合、正常な応答に使用されるものと同じコーデックでエラーのボディも圧縮されます。ドライバーは、サポートするすべてのコーデック (lz4、zstd、gzip、deflate、br/brotli) についてこれをデコードするため、ClickHouseServerException に表示されるメッセージはそのまま読めます。それ以外 (snappy、…) については、コーデック名を示し、元のエラーテキストは system.query_log を参照するよう案内するプレースホルダーを返します。

レスポンスの解凍

Accept-Encoding はサーバーにレスポンスの圧縮を要求するだけであり、それをデコードする処理は別途必要です。ドライバーはレスポンスの Content-Encoding を見て自らデコードするため、通常の読み取り API (ExecuteReaderAsync、ExecuteScalarAsync、ExecuteNonQueryAsync、QueryAsync<T>、Dapper、EF Core、linq2db) はいずれも、圧縮されたレスポンスに対して設定不要でそのまま動作します。デコードに対応しているのは lz4、zstd、gzip、deflate、br です。snappy はサポートされていません。 デフォルトでは、ドライバーは zstd, lz4, gzip, deflate を通知し、ClickHouse は zstd で応答します。別の方式を選びたい場合は、Accept-Encoding を自分で指定します。クライアント全体に適用する場合:
クエリごとに設定でき、そちらが優先されます。
または、ClickHouseClientSettings を直接扱わない ORM 利用者向けに、接続文字列で指定することもできます:
これを設定すると、URL に enable_http_compression=1 も強制的に付与されます。ClickHouse はこれがない限りヘッダーを一切考慮しないためです。これは UseCompression が false の場合も同様です。コーデックを明示的に指定すること自体が、圧縮の要求とみなされるからです。値が未設定の場合、UseCompression=false では Accept-Encoding はまったく送信されません。 Accept-Encoding は 4 か所で設定できます。このうちコーデックを指定している最初のものが優先されます:
  1. QueryOptions.AcceptEncoding (または ClickHouseCommand.AcceptEncoding)
  2. クエリ側の CustomHeaders["Accept-Encoding"]
  3. クライアント側の CustomHeaders["Accept-Encoding"]
  4. ClickHouseClientSettings.AcceptEncoding、または接続文字列キーワードの AcceptEncoding
いずれも指定がない場合、ドライバーはデフォルトの一覧を送信します。コーデックを指定していない値 (null、空文字列、 空白のみ、カンマのみ) は未設定として扱われ、次の候補に処理が移ります。圧縮を無効にするには identity を使用してください。 コーデックを選択するのはクライアントではなくサーバーです。 ClickHouse は Accept-Encoding を走査して token を探し、独自の固定された優先順位 — zstd > br > lz4 > snappy > gzip > deflate — に従って選択します。記述した順序も q 値も無視されます。つまりこのヘッダーは要求ではなく、対応可能なコーデックの通知であり、選択を左右できる唯一の手段はどの token を除外するかだけです。デフォルトには zstd が含まれるため、デフォルトのクエリには zstd で応答されます。残りの token は fallback として機能します。br はデコード可能ですが、デフォルトでは通知されません。 各コーデックが payload サイズ、サーバー CPU、クライアント CPU の面でどう異なるかは、データ、回線、そしてサーバーの http_zlib_compression_level (出荷時のデフォルト: 3) によって変わります。圧縮のチューニングを参照してください。
  • http_zlib_compression_level。 この設定はすべての HTTP コーデックに適用され、デフォルト値は 3 です。この値は、データ、回線速度、CPU 使用率に応じてチューニングしてください。
  • 高速な回線における CPU-bound なクライアント。 ドライバーは呼び出し元の thread 上で response body をデコードするため、ネットワークが bottleneck でない場合は、client-side のデコード速度が制限要因になり得ます。
以下のいずれかに該当する場合は、クエリ単位またはクライアント全体で別のコーデックを要求してください:
判断はレスポンスに基づいて行われるため、リクエスト時に何を指定したかに関わらず、Content-Encoding がそう示していればボディはデコードされます。ヘッダーが存在しない場合や identity の場合はそのまま素通しされ、サポートされているコーデックであればデコードされ、それ以外の場合はその名前を含む error が送出されます。二重デコードのおそれはありません。caller が指定した handler の AutomaticDecompression がすでにボディをデコード済みの場合、Content-Encoding も併せて取り除かれるため、ドライバーからは平文として見え、そのまま何も行われません。 生データの結果はコーデックを通知しません。 ExecuteRawResultAsync (および公開 API の PostStreamAsync / InsertRawStreamAsync) は、ボディをそのまま呼び出し側に渡します。そのため、自分でコーデックを指定しない限り、これらはコーデックを一切要求しません。ドライバー側にこうしたボディをデコードする仕組みはないため、ここでコーデックを提示すると、エクスポート結果が知らないうちに圧縮ファイルになってしまいます。したがってルールは単純で、HttpClient の構成にも左右されません。すなわち、そのまま渡されるボディはサーバーが送信した内容そのままで届き、サーバーはコーデックを要求されない限り平文を送信します。 コーデックを要求すること (クライアント全体またはクエリ単位) が、意図的に圧縮バイト数をエクスポートする手段となります。 明示的な AcceptEncoding (いずれのレベルでも) は生データのリクエストにも適用され、デコードが必要な場合は ClickHouseRawResult.ReadDecompressedStreamAsync() が結果をデコードします。ReadAsStreamAsync、ReadAsByteArrayAsync、ReadAsStringAsync、CopyToAsync は常に、届いたバイト列をそのまま返します。
上記のとおり、返されたストリームはスコープを外れる前に最後まで読み切ってください。レスポンスが圧縮されている場合は、leaveOpen で作成されたデコーダが返されるため、これを破棄してもレスポンス自体は保持されます。圧縮されていない場合は HTTP コンテンツストリームそのものが返されるため、これを破棄するとボディは終了します。いずれの場合も ClickHouseRawResult がレスポンスを所有しています。ストリームを破棄した後は、その他の読み取りメンバーを呼び出さないでください。ClickHouseRawResult の破棄は常に必須であり、それだけで十分です。レスポンスと、ここで挿入されたデコーダの両方を解放します (デコーダはプールされたバッファを保持しています) 。したがって、上記の await using は任意ですが、記述しておいても問題ありません。連続して繰り返し呼び出すと同じストリームが返されます。この型は同時実行での使用には対応していません。 実行可能なサンプルは Select_007_ResponseCompression.cs を参照してください。

Insert (リクエスト) 圧縮

insert のデフォルトコーデックは Zstd です。InsertOptions.Compressor の初期値は ZstdCompressor.Default (レベル 3 の zstd) です。コーデックを変更するには別の compressor を指定し、ボディを非圧縮で送信するには null を指定します。
ドライバーには4つのコーデックが同梱されています。それぞれに Default インスタンスと、レベルおよび書き込みバッファのサイズを受け取るコンストラクターが用意されています。
コンプレッサーのインスタンスは共有してください。 各 Default は 1 つの共有インスタンスであり、4 つのコンプレッサーはいずれも複数のスレッドから同時に使用しても安全です。 InsertOptions.MaxDegreeOfParallelism が 1 を超える場合がまさにこれに該当します。1 回の insert では、バッチごとに 1 つのコンプレッサーを使用するためです。 いずれも IDisposable を実装していません。Default と同じように、独自のインスタンスを一度だけ生成して再利用してください。
IClickHouseCompressor はpublicであり、実装が提供する必要があるのは次の2つのメンバーのみです:
サーバーは、指定した Content-Encoding を受け入れられる必要があります。残りのメンバー — Decompress、MethodByte、MaxEncodedLength、Encode、Decode — には NotSupportedException を throw するデフォルト実装が用意されているため、コーデックに必要なものだけを override してください。 リクエストの圧縮だけでなくレスポンスボディのデコードも行うには Decompress を実装し、ボディが破損している場合や誤ったフォーマットの場合は、返すストリームから InvalidDataException を raise してください。 InsertOptions.Compressor が制御するのはバイナリ insert のみです。ドライバーのその他のリクエストボディは異なる規則で圧縮され、いずれもこれを経由しません:
  • すべての SQL テキストリクエスト (ExecuteReaderAsync、ExecuteScalarAsync、ExecuteNonQueryAsync、QueryAsync<T>、ExecuteRawResultAsync、ADO.NET layer) は、UseCompression が true の場合、つまりデフォルトでは、ステートメントを Content-Encoding: gzip で送信します。コーデックは設定できません。AcceptEncoding が制御するのはレスポンスのみであるため、選択肢は gzip か非圧縮かのどちらかです。Compression=false の場合、ステートメントは平文で送信されます。ステートメントは小さいため通常は気にする必要はありませんが、proxy やパケットキャプチャでリクエストを確認する際には知っておくと役立ちます。
  • マルチパートボディ — parameters を form data として送信するクエリ (UseFormDataParameters=true) — は、UseCompression の設定にかかわらず、常に非圧縮で送信されます。
  • 生データのアップロード (InsertRawStreamAsync、PostStreamAsync) は呼び出しごとの独自のフラグに従い、UseCompression も InsertOptions.Compressor も参照しません。フラグが設定されていれば gzip、そうでなければ非圧縮です。なお、InsertRawStreamAsync の useCompression parameter はデフォルトが true であるため、false を明示的に渡さない限り、生データのアップロードは gzip 圧縮されます — client 側で Compression=false としている場合でも同様です。

圧縮のチューニング

圧縮は、CPU を消費する代わりに転送バイト数を削減するトレードオフです。これが有利に働くかどうかは、コーデック の処理速度に対してネットワーク回線がどれだけ速いかでほぼ決まります。あらゆるケースに当てはまる万能な設定はありません。

判断を左右する唯一の数値

圧縮は、コーデックがネットワークより高速である限り価値があります。 このしきい値は、読み取りパスにおいては多くの人が想定するよりも低くなります。ClickHouse は HTTP レスポンスを出力バッファ内でシングルスレッドで圧縮するためです。16 vCPU の ClickHouse Cloud サービス (hits、RowBinary、レベル 3) で測定したところ、サーバーはおよそ 100〜200MB/s の速度で圧縮済み出力を生成します。 したがって、結果セットが大きく、かつ同時に処理されるクエリが 1 つだけだと仮定すると、圧縮が割に合わなくなるのはおおむね 100MB/s 付近です。単一の HTTPS ストリームは、同一クラウドリージョン内であればこれを上回ることが一般的ですが、public internet や VPN、リージョン境界をまたぐ場合は通常これを下回ります。 挿入パスでは、より高速な回線でも圧縮が有効なままです。クライアント側は専用のコアで圧縮を行うため、通常はサーバー側のレスポンス圧縮よりも高速だからです。

デプロイメント別の大まかな目安

この表では表現しきれない点が3つあります:
  • エグレスコスト: データ転送に課金される場合、レイテンシとは別に転送バイト数そのものにコストが発生するため、回線速度に関わらず圧縮率を高める方向に働きます。
  • 小さな結果: 上記はいずれも大きなペイロードを前提とした話です。小さなレスポンスではコーデックの違いはほとんど影響せず、リクエストごとのオーバーヘッドが支配的になります。
  • 並列挿入は挿入側のしきい値を押し上げます。 上記のスループット値はすべて単一スレッドでの値です。InsertOptions.MaxDegreeOfParallelism のデフォルトは 1 ですが、これを引き上げるとバッチが同時実行で圧縮されるため、クライアント全体のエンコード速度は割り当てたコア数にほぼ比例して向上します。そのため高速な回線では、シングルスレッドの挿入なら圧縮が割に合わなくなる速度をはるかに超えても、並列挿入であれば圧縮する価値が残ります。表の挿入の行は下限として捉え、すでに並列でバッチ処理している場合は、回線が速すぎて圧縮は不要だと結論づける前に改めて測定してください。
読み取りパスは、複数のクエリ間でのみ並列化されます。

コーデック の選択

Levels

レスポンスの圧縮は、単一の server setting である http_zlib_compression_level によって制御されます。これは zlib だけでなく、すべての HTTP コーデック に適用されます。デフォルトは 3 です。 計測による明確な根拠がない限り、この値は変更しないでください。デフォルトより上げても、CPU を大量に消費する割にサイズはほとんど縮みません (zstd の場合、3 → 6 でバイト数は約 14% 減るだけで、server の CPU 使用量はおよそ 2 倍になります) 。br に至っては極端に悪化します。逆にレベル 1 まで下げると状況は一変し、lz4 のコストは大幅に下がり、zstd は lz4 に対する CPU 面での優位性を失います。必要であれば クエリ ごとに設定してください:

自分の環境でクロスオーバーポイントを測定する

codecと圧縮レベルの選択を最適化する最も手軽な方法は、複数のcodecで同じクエリの実行時間を計測して比較することです。
同じ状況をサーバー側から確認するには、system.query_log から ProfileEvents を読み出します。該当する行を特定できるように、QueryOptions.QueryId を設定してください:
自分でベンチマークを取る際の落とし穴が1つあります。ORDER BY を伴わない単なる LIMIT n は、実行のたびに異なる行 を返すため、繰り返すたびに異なるデータが圧縮され、比率が単なるノイズになってしまいます。固定された結果セットを対象に比較してください。

生データストリームによる挿入

InsertRawStreamAsync を使用すると、CSV、JSON、Parquet などのサポートされている ClickHouse フォーマットで、ファイルストリームやメモリストリームから直接データを挿入できます。 CSV ファイルから挿入する:
ドライバーはストリームの所有権を取得します。 InsertRawStreamAsync と PostStreamAsync は、リクエストが成功したか失敗したかにかかわらず、完了時点で渡されたストリームを破棄します。自分で破棄したり、その後に再利用したりしないでください。上の例で FileStream を using で囲んでいないのはこのためです。自分で書いた using は、ドライバーがストリームを破棄した後に実行されます。FileStream や MemoryStream であればこの2回目の呼び出しは無害ですが、Dispose でプールされたバッファーを返却したり参照カウントを減らしたりするストリームの場合、リソースを二重に解放することになります。所有権が移るのは引数が受理された時点です。テーブル、ストリーム、フォーマットの指定漏れによって呼び出しが ArgumentException や ArgumentNullException をスローした場合、ストリームは依然として呼び出し側のものです。
データインジェストの動作を制御するオプションについては、フォーマット設定のドキュメントを参照してください。

その他の例

さらに実践的な使用例については、GitHubリポジトリ内のexamplesディレクトリを参照してください。

ADO.NET

このライブラリは、ClickHouseConnection、ClickHouseCommand、ClickHouseDataReader を通じて、ADO.NET を完全にサポートしています。この API は、ORM インテグレーション (Dapper、Linq2db) や、標準的な .NET のdatabase 抽象化が必要な場合に不可欠です。

ClickHouseDataSource によるライフタイム管理

適切なライフタイム管理と接続プーリングを確実に行うため、接続は必ず ClickHouseDataSource から作成してください。 DataSource は内部で単一の ClickHouseClient を管理しており、すべての接続はその HTTP 接続プールを共有します。
依存関係の挿入では:
本番環境のコードで ClickHouseConnection を直接作成しないでください。直接インスタンス化するたびに、新しい HTTP クライアントと接続プールが作成されるため、高負荷時にソケットが枯渇するおそれがあります。
代わりに、必ず ClickHouseDataSource を使用するか、単一の ClickHouseClient インスタンスを共有してください。

ClickHouseCommand の使用

SQL を実行するコマンドを接続から作成します。
コマンド メソッド:
  • ExecuteNonQueryAsync() - INSERT、UPDATE、DELETE、DDL ステートメントに使用します
  • ExecuteScalarAsync() - 最初の行の最初のカラムを返します
  • ExecuteReaderAsync() - 結果を反復処理するための ClickHouseDataReader を返します

ClickHouseDataReader の使用

ClickHouseDataReader を使用すると、クエリ結果に型付きでアクセスできます。

enum の序数を読み取る

Enum8 または Enum16 カラムはラベルとしてマテリアライズされます。GetFieldType は string を返し、 GetString、GetValue、GetFieldValue<string> はいずれもラベルを返します。保持されている値は文字列であるため、 数値系のアクセサは enum カラムに対して InvalidCastException をスローします。 ラベルの背後にある数値を取得するには TryGetEnumOrdinal を使用します。
Enum8/Enum16 カラム、および cell が NULL でない Nullable(Enum...) カラムの場合は、true を返し、value を設定します。NULL の cell や enum 以外のカラムの場合は、value に 0 を設定したうえで false を返します。序数は wire 上の signed な値であるため負の値になることがあり、Enum16 の序数は 1 バイトに収まらない場合があります。

ベストプラクティス

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

ClickHouse.Driver は内部で System.Net.Http.HttpClient を使用しています。HttpClient にはエンドポイントごとの接続プールがあります。そのため、次の点に注意してください。
  • データベースセッションは、接続プールで管理される HTTP 接続を介して多重化されます。
  • HTTP 接続はプールによって自動的に再利用されます。
  • ClickHouseClient または ClickHouseConnection オブジェクトを破棄した後でも、接続が維持される場合があります。
推奨パターン:
カスタムの HttpClient または HttpClientFactory を使用する場合は、半閉状態の接続によるエラーを避けるため、PooledConnectionIdleTimeout をサーバーの keep_alive_timeout より小さい値に設定してください。Cloud デプロイメントの既定の keep_alive_timeout は 10 秒です。
共有の HttpClient を使わずに、複数の ClickHouseClient や単独の ClickHouseConnection インスタンスを作成するのは避けてください。各インスタンスはそれぞれ独自の接続プールを作成します。

DateTime の扱い

  1. 可能な限り UTC を使用します。 タイムスタンプは DateTime('UTC') カラムとして保存し、コードでは DateTimeKind.Utc を使用します。これにより、タイムゾーンの曖昧さを排除できます。
  2. タイムゾーンを明示的に扱うには DateTimeOffset を使用します。 DateTimeOffset は常に特定の時点を表し、オフセット情報を含みます。
  3. SQL の型ヒントでタイムゾーンを指定します。 Unspecified の DateTime 値を持つパラメーターを使用して非 UTC カラムを対象とする場合は、SQL にタイムゾーンを含めます。

非同期 INSERT

非同期 INSERT では、バッチ化の責任がクライアントからサーバーに移ります。クライアント側でバッチ化する代わりに、サーバーが受信データをバッファに保持し、設定可能なしきい値に基づいてストレージに書き出します。これは、多数のエージェントが小さなペイロードを送信するオブザーバビリティのワークロードのような、高い同時実行性が求められるシナリオで有効です。 CustomSettings または接続文字列で非同期 INSERT を有効にします:
2 つのモード (wait_for_async_insert で制御) :
wait_for_async_insert=0 では、エラーはフラッシュ時にのみ表面化するため、元の insert までさかのぼって特定できません。また、クライアント側でバックプレッシャーもかからないため、サーバーの過負荷を招くおそれがあります。
主な設定:

セッション

セッションは、状態を保持するサーバー側の機能が必要な場合にのみ有効にしてください。例:
  • 一時テーブル (CREATE TEMPORARY TABLE)
  • 複数のステートメントにまたがってクエリコンテキストを維持する
  • セッションレベルの設定 (SET max_threads = 4)
セッションを有効にすると、同じセッションの同時使用を防ぐため、リクエストは直列化されます。そのため、セッション状態を必要としないワークロードではオーバーヘッドが発生します。
ADO.NET を使用する場合 (ORM との互換性のため) :

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

ClickHouse.Driver は、ClickHouse のすべてのデータ型をサポートしています。以下の表は、データベースからデータを読み取る際の ClickHouse の型とネイティブの .NET 型の対応関係を示しています。

型マッピング: ClickHouseから読み取る場合

整数型


浮動小数点型


Decimal 型

Decimal 型の変換は UseCustomDecimals 設定で制御されます。

Boolean 型


String 型

デフォルトでは、String と FixedString(N) の両方のカラムは string として返されます。代わりに byte[] として読み取るには、接続文字列で ReadStringsAsByteArrays=true を設定します。これは、有効な UTF-8 でない可能性があるバイナリデータを保存する場合に便利です。この設定は他の型の内部にネストされた文字列にも適用されるため、Array(String) は byte[][] として、Map(String, String) は Dictionary<byte[], byte[]> として読み取られます (キーも含みます) 。唯一の例外は JSON カラムで、その文字列のリーフは常にテキストとして扱われます。JSON type を参照してください。

日付と時刻の型

ClickHouse では、DateTime と DateTime64 の値は内部的に Unix timestamp (epoch からの秒、またはその下位単位) として保存されます。保存は常に UTC ですが、カラムにはタイムゾーンを関連付けることができ、これによって値の表示方法や解釈方法が変わります。 DateTime の値を読み取る際、DateTime.Kind プロパティはカラムのタイムゾーンに基づいて設定されます。 UTC 以外のカラムでは、返される DateTime はそのタイムゾーンでのローカル時刻を表します。そのタイムゾーンに対する正しいオフセットを持つ DateTimeOffset を取得するには、ClickHouseDataReader.GetDateTimeOffset() を使用してください。
明示的なタイムゾーンを 持たない カラム (つまり DateTime('Europe/Amsterdam') ではなく DateTime) については、ドライバーは Kind=Unspecified の DateTime を返します。これにより、タイムゾーンについて何も仮定せず、保存されている時刻の値をそのまま正確に保持できます。 明示的なタイムゾーンを持たないカラムでタイムゾーンを考慮した動作が必要な場合は、次のいずれかを行ってください。
  1. カラム定義で明示的なタイムゾーンを使用する: DateTime('UTC') または DateTime('Europe/Amsterdam')
  2. 読み取り後に自分でタイムゾーンを適用する。

JSON 型

JSON カラムの戻り値の型は、JsonReadMode 設定で制御されます。
  • Binary (デフォルト): System.Text.Json.Nodes.JsonObject を返します。JSON データを構造化された形で扱えますが、特殊な ClickHouse 型 (IP アドレス、UUID、高精度の Decimal 値など) は、JSON 構造内では文字列表現に変換されます。
  • String: 生の JSON を string として返します。ClickHouse の JSON 表現をそのまま保持できるため、JSON をパースせずにそのまま受け渡したい場合や、デシリアライゼーションを自分で処理したい場合に便利です。
None は 3 つ目のモードです。読み取り時の動作は Binary とまったく同じですが、クエリと共に server setting を送信しません。server setting の設定が許可されていない接続で使用してください。 カラム型で宣言されたパスは 型付きパス であり、ドキュメント内のそれ以外のパスは 動的パス です。この 2 つは、値が null の場合に挙動が異なります。 型付きパスは常に JsonObject に現れます。Nullable(T) または Dynamic として宣言した場合、格納された値が null のときも、ドキュメントにそのパスが存在しないときも、どちらも JSON の null として返されるため、両者を区別することはできません:
非 Nullable な型で宣言されている場合、存在しないパスは代わりにその型のデフォルト値になります — JSON(x String) では {"x":""}、JSON(x Int64) では {"x":0} となります。 値が null の動的パスはオブジェクトから完全に削除されるため、ContainsKey はそのパスに対して false を返します。プレーンな JSON カラムから {"x":null} を読み取ると {} になります。 ネストされた型付きパスは親を構築するため、JSON(a.b Nullable(Int64)) では空のドキュメントであっても {"a":{"b":null}} が得られます。
これは server 自体がレンダリングする内容であるため、Binary モードと String モードの結果は現在一致しています。1.4.0 より前では、null を保持する型付きパスは JsonObject から削除されていたため、{"x":null} は {} として読み戻されていました — さらに JSON(a.b Nullable(Int64)) のようなネストされたパスでは、a のサブツリー全体が消えていました。
JSON カラム内の String リーフは、ReadStringsAsByteArrays の設定値にかかわらず、常にテキストとして返されます。JsonValue にはバイト配列形式が存在せず、byte[] は base64 として出力されてしまうためです。これは String、FixedString、およびそれらを LowCardinality、Nullable、SimpleAggregateFunction でラップしたもの、さらに Array や Map 内の文字列 (map のキーを含む) にも当てはまります。
JSON リーダーが型を判別できないバイト配列は、やはり base64 として出力されます。Variant や Dynamic の型付きパスは、型が行ごとにしか判明しない値を保持するため、Variant(Array(UInt8), String) 配下の文字列は base64 エンコードされて返されます。これはどちらの設定でも同じです。JSON の map キー型が厳密に String 以外の場合 (例えば Map(LowCardinality(String), String)) は、NotSupportedException がスローされます。
ClickHouse では、あるパスを値として宣言すると同時に別のパスの親としても宣言するカラムを受け付けます。たとえば JSON(a Int64, a.b Int64) です。どちらのパスもすべての行に存在するため、サーバーはキーが重複した行を出力します: {"a":0,"a":{"b":7}}。JsonObject は 1 つのキーに対して 2 つの値を保持できないため、JsonReadMode.Binary は該当する 2 つのパスを示す SerializationException をスローします。値が Map の場合も同様で、動的な a.b を併せ持つ行から JSON(a Map(String, Int64)) を読み取る場合が該当します。 これが当てはまるのは、その行で両側が値を保持している場合のみです。何も保持しない側 — null、空のオブジェクト、または値がすべて null であるサブツリー — は、サーバーが 2 つのパスのどちらを先に送信するかにかかわらず、データを持つ側に譲ります。そのため、Nullable 型で宣言された重複は行ごとにどちらか一方の側だけが埋まり、エラーなく読み取れます: JSON(a Nullable(Int64), a.b Nullable(Int64)) は期待どおり {"a":5} と {"a":{"b":7}} を返します。 このようなカラムを JsonReadMode.String で読み取れば、重複キーを含めてサーバーの JSON テキストをそのまま取得できます。 AllowDuplicateJsonKeys を設定すると、例外をスローせずにカラムを JsonObject として読み取り続けます。この場合、ドライバーは行が保持する 2 つの値のうち後に現れた方だけを残して他方を破棄するため、結果は情報が失われたものになります: {"a.b":7} を保持する JSON(a Int64, a.b Int64) は {"a":0} として読み取られます。値を保持するパスの親が scalar または Array を保持している場合は、そのいずれの下にもサブツリーを配置できないため、依然として例外がスローされます。

Map type

ClickHouse の Map(K, V) は物理的には Array(Tuple(K, V)) であり、同じキーを持つエントリを複数保持できます。一方、Dictionary ではそれができないため、デフォルトのモードでは重複したキーは最後の値のみが残り、それより前のペアは破棄されます。MapReadMode 設定で表現形式を選択できます:
  • Dictionary (デフォルト): Dictionary<K, V> を返します。
  • KeyValuePairs: サーバーがペアを送信した順序で List<KeyValuePair<K, V>> を返すため、キーが重複するエントリも含めてすべてのペアが保持されます。
mode は Map カラムのフレームワーク型を選択するものであり、GetFieldValue<T>、ドライバーが報告するスキーマの型、POCO のプロパティマッピングにも適用されます。カラムの型ツリー内に map が現れる箇所すべてに適用され、Array(Map(...))、Map(K, Map(...))、Tuple(..., Map(...))、Dynamic も対象に含まれます。 書き込みパスでは、どちらの mode でも両方の表現が受け入れられます。maps の書き込み を参照してください。

その他の型

Dynamic 型と Variant 型は、各行で実際に使用されている基底型に対応する型に変換されます。

ジオメトリ型

Geometry 型は、任意のジオメトリ型を保持できる Variant 型で、対応する型に変換されます。

型マッピング: ClickHouse への書き込み

データの挿入時に、ドライバーは .NET 型を対応する ClickHouse 型に変換します。以下の表は、各 ClickHouse カラム型で受け入れ可能な .NET 型を示しています。

整数型


浮動小数点型


ブール型


文字列型


日付と時刻の型

範囲外の値バイナリ書き込みパスでは、サポート範囲外の Date、Date32、DateTime、DateTime32 の値は、Write 時に ArgumentOutOfRangeException をスローし、カラム型とサポート範囲がエラーメッセージに含まれます。以前は、範囲外の値が 32 ビット整数を介して暗黙的に切り詰められ、サーバーによって再解釈されることで、実在はするものの誤った timestamp が生成される可能性がありました。
ドライバーは値の書き込み時に DateTime.Kind を考慮します。 DateTimeOffset の値では、常に正確な時点が保持されます。 例: UTC DateTime (時点は保持される)
例: 未指定の DateTime (時計上の時刻)
推奨事項: 最もシンプルで予測しやすい動作にするため、すべての DateTime 操作で DateTimeKind.Utc または DateTimeOffset を使用してください。これにより、サーバーのタイムゾーン、クライアントのタイムゾーン、またはカラムのタイムゾーンに関係なく、コードが常に一貫して動作します。

HTTP パラメータと Bulk Copy の違い

Unspecified の DateTime 値を書き込む際、HTTP パラメータのバインドと Bulk Copy には重要な違いがあります。 Bulk Copy は対象カラムのタイムゾーンを認識しており、そのタイムゾーンで Unspecified の値を正しく解釈します。 HTTP Parameters はカラムのタイムゾーンを自動的には認識しません。SQL の型ヒントでそのタイムゾーンを指定する必要があります。

Decimal 型


JSON 型

JSON の書き込み時の動作は、JsonWriteMode 設定で制御されます。
  • String (デフォルト): string、JsonObject、JsonNode、または任意のオブジェクトを受け付けます。すべての入力は System.Text.Json.JsonSerializer でシリアライズされ、サーバー側でパースするために JSON 文字列として送信されます。これは最も柔軟なモードで、型登録なしで動作します。
  • Binary: 登録済みの POCO 型のみを受け付けます。データはクライアント側で、完全な型ヒントのサポート付きで ClickHouse のバイナリ JSON フォーマットに変換されます。使用する前に connection.RegisterJsonSerializationType<T>() を呼び出す必要があります。このモードで string または JsonNode の値を書き込むと、ArgumentException がスローされます。
JSON カラムに型ヒント (例: JSON(id UInt64, price Decimal128(2))) がある場合、ドライバーはそれらのヒントを使って、値を型情報を完全に保ったままシリアライズします。これにより、UInt64、Decimal、UUID、DateTime64 など、汎用的な JSON としてシリアライズすると精度が失われる可能性のある型でも、精度を保持できます。 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 でのみ機能します。
プロパティ名とカラムの型ヒントの照合では、大文字と小文字が区別されます。プロパティ 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 のネスト型 (Nested(...)) は、配列として読み書きできます。

ログと診断

ClickHouse の .NET クライアントは、Microsoft.Extensions.Logging の抽象化レイヤーと統合されており、軽量で必要に応じて有効化できるログ機能を提供します。有効にすると、ドライバーは接続ライフサイクル イベント、コマンド実行、トランスポート処理、バルク挿入操作に関する構造化メッセージを出力します。ログ機能は完全に任意であり、ロガーを設定していないアプリケーションも追加のオーバーヘッドなしでそのまま動作し続けます。

クイックスタート

appsettings.json を使用する

.NET の標準構成機能を使用してログレベルを設定できます。

インメモリ構成を使用する

コード内で、カテゴリ別にログ出力の詳細度を設定することもできます。

カテゴリと出力元

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

例: 接続の問題を診断する

以下の内容がログに記録されます。
  • HTTP クライアント ファクトリの選択 (既定のプールまたは単一接続)
  • HTTP ハンドラーの設定 (SocketsHttpHandler または HttpClientHandler)
  • 接続プールの設定 (MaxConnectionsPerServer、PooledConnectionLifetime など)
  • タイムアウトの設定 (ConnectTimeout、Expect100ContinueTimeout など)
  • SSL/TLS の設定
  • 接続のオープン/クローズ イベント
  • セッション ID の追跡

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

ネットワーク関連の問題の診断に役立てるため、ドライバーライブラリには .NET のネットワーク内部処理の低レベルトレースを有効にするヘルパーが含まれています。これを有効にするには、レベルを Trace に設定した LoggerFactory を渡し、EnableDebugMode を true に設定する必要があります (または ClickHouse.Driver.Diagnostic.TraceHelper クラスを使って手動で有効にします) 。イベントは ClickHouse.Driver.NetTrace カテゴリにログ出力されます。警告: これにより非常に大量の logs が生成され、パフォーマンスに影響します。本番環境でデバッグモードを有効にすることは推奨されません。

OpenTelemetry

このドライバーは、.NET System.Diagnostics.Activity API を通じて、OpenTelemetry の分散トレーシングをネイティブにサポートしています。有効にすると、ドライバーはデータベース操作に対するスパンを出力し、それらを Jaeger や ClickHouse 自体 (OpenTelemetry Collector 経由) などのオブザーバビリティバックエンドにエクスポートできます。

トレーシングを有効にする

ASP.NET Core アプリケーションでは、ClickHouse ドライバーの ActivitySource を OpenTelemetry の設定に追加します。
コンソールアプリケーション、テスト、または手動セットアップの場合:

スパン属性

各スパンには、標準的なOpenTelemetryのデータベース属性に加え、デバッグに利用できるClickHouse固有のクエリ統計情報が含まれます。

設定オプション

ClickHouseDiagnosticsOptions を使用して、トレーシングの動作を制御します。
IncludeSqlInActivityTags を有効にすると、トレースに機密データが含まれる可能性があります。本番環境での使用には注意してください。

TLS 設定

HTTPS 経由で ClickHouse に接続する場合、TLS/SSL の挙動はいくつかの方法で設定できます。

カスタム証明書の検証

本番環境でカスタムの証明書検証ロジックが必要な場合は、ServerCertificateCustomValidationCallback ハンドラーを構成した独自の HttpClient を指定します。
カスタム HttpClient を指定する際の注意事項
  • 自動圧縮解除: AutomaticDecompression は無効のままにしてください。圧縮されたレスポンスはドライバー自身がデコードするため不要であり、有効にするとリクエスト側でかえって不都合が生じます。送信時にハンドラーがマスクに含まれるすべてのアルゴリズムを送信する Accept-Encoding に追加してしまうため、ドライバーが提示した範囲が広がり、ClickHouse が要求していない codec で応答する可能性があります。レスポンスの圧縮解除を参照してください。
  • アイドルタイムアウト: ハーフオープン接続による接続エラーを避けるため、PooledConnectionIdleTimeout はサーバーの keep_alive_timeout (ClickHouse Cloud では 10 秒) より短く設定してください。

パフォーマンスチューニング

このセクションでは、クライアントで最適なパフォーマンスを引き出す方法と、個々のユースケースに合わせてクライアントのパフォーマンスを高めるために調整できるさまざまなオプションについて説明します。

一目でわかる要点

| 目的 | 対応方法 | |---|---|---| | 行を POCO に読み込む | MapTo<T> ではなく QueryAsync<T> を使う | | 大量の 挿入 を行う | InsertOptions.BatchSize を大きくする | | 挿入 中心のコンソールアプリや worker アプリを実行する | サーバー GC を有効にする | | ネットワーク越しに大きな結果を読み取る | レスポンスの圧縮を有効なままにする(デフォルト) | | 高速な回線経由で 挿入 する | InsertOptions.Compressor = null を試す | | 同じテーブルに何度も 挿入 する | UseSchemaCache または ColumnTypes を使う | | 非常に大きな結果を読み取る | ReadBufferSize を大きくする |

読み取り: マテリアライズ経路の選択

結果から行を取得する方法は3つあり、コストはそれぞれ異なります。一部の経路では値がボックス化されるため、割り当てが増えパフォーマンスが低下します。 hits データセットの105カラムを1,000,000行読み取った場合:
ORM は型付きアクセサを使用する場合に高速パスを利用できます。 linq2db は各カラムに対して GetInt64、 GetDouble、GetDateTime を登録するため、ボックス化なしで読み取ります。 GetValue 経由で読み取るコード (Dapper の dynamic 結果を含む) は、値ごとにボックス化が発生します。ORM のクエリがホットパスにあり、 GetValue 経由で読み取っている場合は、そのクエリに限り QueryAsync<T> を使用してください。

挿入: バッチサイズと並列度

バッチサイズは、挿入スループットを左右する最も影響の大きい制御項目です。InsertOptions.BatchSize のデフォルトは 100,000 行です。 大きなバッチを使用してください。 1,000,000 行の挿入で、バッチあたりの行数を 10,000 から 100,000 に増やした結果は次のとおりです。 バッチサイズを制御できない場合 (多数の小規模なプロデューサーがそれぞれ独立して行を送信する場合など) は、非同期 挿入 を使用し、バッチ化はサーバーに任せてください。 並列アップロード。 InsertOptions.MaxDegreeOfParallelism のデフォルトは 1 です。この値を増やすと、複数のバッチを同時に送信できます。各バッチがそれぞれ別のスレッドで圧縮されるため、圧縮を有効にしている場合に特に効果的です。セッションは並列挿入では機能しません。セッションを無効にするか、MaxDegreeOfParallelism = 1 のままにしてください。 スキーマプローブをなくす。 InsertBinaryAsync の呼び出しでは、カラム型を判別するために毎回まず SELECT ... WHERE 1=0 クエリが送信されます。ColumnTypes または UseSchemaCache によってこのラウンドトリップをなくす方法は、スキーマプローブクエリのスキップ を参照してください。
ボックス化を伴わない挿入パスは、デフォルトの RowBinary フォーマットに適用されます。RowBinaryWithDefaults では DBDefault マーカーを見つけるために各値を検査する必要があるため、低速なパスのままとなります。

圧縮: 2つの方向で結論が異なる

圧縮とは、CPUを消費して転送バイト数を減らすトレードオフです。このトレードオフが有利になるかどうかは、転送の方向、ClickHouse serverへのconnectionのbandwidth、選択した圧縮 algorithmとデータの相性、そして転送バイトごとに課金が発生するかどうかによって決まります。 Reads: serverが同一マシン上で動作している場合を除き、圧縮は有効のままにしてください。これがデフォルトです。圧縮なしの場合と比較すると、レベル1のzstdでは次の結果が得られました。 挿入: 圧縮を有効にする前に必ず測定してください。有効化に見合うだけの削減効果が得られないこともあります。また、伸長処理はserverに追加の負荷をかける点にも留意してください。ZstdやLZ4であれば負荷は軽微ですが、他のアルゴリズム(例: Brotli)では高くなることがあります。 挿入時の圧縮を無効にするには、次のようにします。
codecの選択、圧縮レベル、そして自身の環境における交差点の見つけ方については、圧縮のチューニングを参照してください。

Buffers

ReadBufferSize は、HTTP レスポンスを読み取るバッファのサイズを設定します。デフォルトは 64 KiB です。 ドライバーはこのバッファを共有プールから借用し、リーダー を破棄する際に返却するため、クエリごとに割り当てが発生することはありません。この値を大きくすると、大きな結果セットでのバッファ再充填の回数を減らせます。ドライバーは同時にオープンしている リーダー ごとに 1 つのバッファを保持するため、メモリ使用量はバッファサイズと同時実行される リーダー の数に比例して増加します。
リーダーは必ず破棄してください。 リーダーを破棄すると、プールされたバッファが返却され、HTTP接続が解放されます。破棄せずに放置すると、バッファはプールに返却されず、HTTP接続が使用不可のまま残る可能性があります。通常のガベージコレクションは破棄の代わりにはなりません。

ランタイムと GC

挿入 の多いアプリケーションでは Server GC を有効にしてください。 同一のコード、同一の割り当てバイト数であっても、Workstation GC は Server GC に比べて 挿入 が最大 97% 低速でした。
ASP.NET Core プロジェクトでは、この設定がすでに有効になっています。一方、コンソールアプリケーション、ワーカーサービス、および大半のコンテナーイメージでは有効になっていません。 原因は generation 0 の予算サイズにあります。Workstation GC は予算が小さいため、挿入 が生成する短命なバッファが generation 0 で回収されません。代わりに generation 1 へ昇格し、その結果プロモーションが増えて generation 2 の処理が大幅に増加します。ある 挿入 のケースでは、1,000 操作あたりの generation 2 のコレクション回数は、Server GC で 4,000 回、Workstation GC で 73,000 回でした。
Server GC は スループット のための設定であり、レイテンシ のための設定ではありません。同じ計測では、Server GC は一時停止の合計時間が半分未満であった一方、個々の一時停止は長くなりました (95 パーセンタイルで 114.6 ms 対 61.9 ms) 。サービスが tail レイテンシ の影響を受けやすい場合は、いずれかを選ぶ前に両方の mode を計測してください。

レイテンシ: 接続を再利用する

新しい TCP 接続の確立と TLS ハンドシェイクには、かなりの時間がかかります。 接続を再利用すれば、クエリのレイテンシを大幅に削減できます。
  • リクエストごとにクライアントを作成しないでください。独自の HttpClient を持つクライアントを新たに作成するたびに、新しい接続プールが作られ、 ハンドシェイクのコストが再び発生します。アプリケーションの存続期間を通じて 1 つの ClickHouseClient を使い回してください。これはスレッドセーフであり、 シングルトンとしての利用を想定して設計されています。
  • ADO.NET や ORM では ClickHouseDataSource を使用し、すべての接続が 1 つのプールを共有するようにしてください。
パターンの全体像については、 接続のライフタイムとプーリングを参照してください。

自分で計測する

多くの場合、パフォーマンスはデータの形状、サーバーへの接続速度、 クライアント側のCPUとサーバー側のCPUのどちらを優先するか (あるいはその逆か) 、ハードウェアの制約などによって変わります。 そのため、実際のデータと環境に基づいて、ご自身でパフォーマンスを計測することを推奨します。 サーバー側が担った処理を確認するには、QueryOptions.QueryId を設定し、カウンターを読み取ります:

ORM サポート

ORM では ADO.NET API (ClickHouseConnection) が必要です。接続のライフサイクルを適切に管理するため、ClickHouseDataSource から接続を作成してください。

Dapper

ClickHouse.Driver は Dapper に対応しています。ドライバーは、Dapper の @parameter 構文を ClickHouse のネイティブな {parameter:Type} 構文に自動変換し、型は .NET の値から推論されます。 適切に connection のライフタイムを管理するには、ClickHouseDataSource を使用します。

パラメーターの受け渡し形式

標準的な Dapper のパラメーター指定方法をすべてサポートしています。 匿名オブジェクト:
POCO クラス:
Dictionary:
DynamicParameters (ディクショナリまたは匿名オブジェクトから) :

POCO へのクエリ

Dapper は、カラム名に基づいてプロパティにマッピングします (大文字と小文字を区別しません) 。

ClickHouseネイティブのパラメーター構文

型を明示的に制御する必要がある場合は、パラメーター値に Dictionary<string, object> を使用し、SQL 内で ClickHouse の {param:Type} 構文を直接使用してください。同じパラメーターに対して @param 構文と {param:Type} 構文を併用しないでください。

WHERE IN

DapperのネイティブなIN展開が機能します:
Dapper はこれを WHERE id IN (@Ids1, @Ids2, @Ids3) に書き換え、ドライバーが展開された各パラメーターをそれぞれ変換します。 Array パラメーターを使った ClickHouse の has() も動作します:

カスタム型ハンドラー

ITuple、BigInteger、ClickHouseDecimal など、一部の ClickHouse の型では、起動時にハンドラーを登録する必要があります。
型ハンドラーの実装例は、Dapper の例を参照してください。

Dapper.Contrib

GetAll<T>() と Get<T>(id) は動作します。Insert<T>() は動作しません。これは SQL Server の構文 (SCOPE_IDENTITY、[]) を生成するためです。代わりに、ClickHouseClient のネイティブな InsertBinaryAsync メソッドを使用することを推奨します。
プロパティ名は ClickHouse のカラム名と完全に一致している必要があります (大文字と小文字を区別します) 。

制限事項

Linq2db

このドライバーは、.NET 向けの軽量な ORM/LINQ プロバイダーである linq2db に対応しています。詳細なドキュメントについては、プロジェクトの Web サイトを参照してください。 使用例: ClickHouse プロバイダーを使用して DataConnection を作成します。
テーブルのマッピングは、属性または Fluent API を使用して定義できます。クラス名とプロパティ名がテーブル名およびカラム名と完全に一致している場合は、設定は不要です。
クエリの実行:
バルクコピー: 効率的な一括挿入には BulkCopyAsync を使用します。

Entity Framework Core

ClickHouse 向けの公式 Entity Framework Core プロバイダーです。C# クラスを ClickHouse テーブルにマッピングし、LINQ でクエリを実行し、SaveChanges を通じてデータを insert できます。いずれも使い慣れた EF Core のパターンで行えます。
このプロバイダーは現在も活発に開発が進められています。現行の release では、LINQ クエリ (JOIN、subqueries、set operations を含む) 、SaveChanges / BulkInsertAsync による INSERT、完全な DDL (CREATE / ALTER / DROP) を伴う移行、そして ClickHouse 固有の table engine 設定をサポートしています。UPDATE / DELETE には対応していません。

インストール

.NET 10.0 と EF Core 10 が必要です。

クイックスタート

エンティティとDbContextを定義し、LINQ でクエリを実行します。

サポートされる型

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 の関数に変換されます — 算術、対数、三角、およびユーティリティ関数。 このプロバイダーは、JOIN の挙動に関する Entity Framework の想定に合わせるため、接続のたびに set_join_use_nulls=1 を自動的に設定します。 ClickHouse サーバーまたは profile でこの設定の変更が禁止されている場合 (例: readonly=1 の profile) 、次のように無効化してください。
オプトアウトが有効な場合、LEFT JOIN は ClickHouse のカラムのデフォルト値を返すため、EF の null ベースのナビゲーション検出は期待どおりに機能しなくなります。== null の代わりに、0 / "" との明示的な比較を使用してください。

データの挿入

SaveChanges では、ドライバーが提供するネイティブの InsertBinaryAsync API を使用します。RowBinary エンコーディングと圧縮されたリクエストボディを利用するため、パラメーター化 SQL よりもはるかに効率的です。
エンティティは、保存後に他の EF Core プロバイダーと同様、Added から Unchanged に変わります。 バッチサイズ は設定できます (デフォルトは 1000) :

一括挿入

高スループットの読み込みでは、SaveChanges ではなく BulkInsertAsync を使用してください。これは DbContext の拡張メソッドで、EF Core の変更トラッカー、ID 解決、状態管理を完全にバイパスし、RowBinary エンコーディングと圧縮されたリクエストボディを使用して、ドライバーの InsertBinaryAsync を直接呼び出します。 そのため、挿入後にエンティティの追跡が不要な大規模データセットの読み込みに適しています。
入力には任意の IEnumerable<T> を使用できます。エンティティはすべてをメモリに読み込むことなく順次処理されます。戻り値は挿入された行数です。挿入後もエンティティは DbContext にアタッチされないため、Added → Unchanged の状態遷移は発生しません。

列挙型

ClickHouse Enum8/Enum16 カラムは、string プロパティまたは C# の enum 型にマッピングできます。C# の列挙型を使用する場合、プロバイダーは列挙型とその文字列表現の間で自動的に変換します。

カスタム型の変換

EF Core の ValueConverter システムを使うと、カスタム型をプロバイダーがすでにサポートしている型にマッピングできます。プロバイダーがカスタム型を直接扱うことはなく、EF Core がその境界で変換を行います。 プロパティ単位の変換:
再利用可能なコンバータークラス:

カラム型のアノテーション

string、int、DateTime などのスカラー型では、プロバイダーが ClickHouse の型を自動的に推論します。パラメーター化された型やラッパーについては、ClickHouse の型を明示的に指定する必要があります。 データ アノテーション (属性) を使用する場合:
OnModelCreating で fluent API を使用する方法:
Array(Nullable(Int32)) や LowCardinality(Nullable(String)) のようなネストされたラッパー型をサポートしています — プロバイダーは Nullable と LowCardinality をどのネストレベルでも自動的にアンラップします。

Variant と Dynamic カラム

ClickHouse の Variant(T1, T2, ...) カラムおよび Dynamic カラムは、.NET では object にマップされます。object は自動的な型推論には汎用的すぎるため、.HasColumnType() でストア型を明示的に指定する必要があります。
読み取り時には、値は保存されている判別子に対応する .NET 型 (例: string、ulong、ulong[]) に自動的にデシリアライズされます。

JSON カラム

このプロバイダーは ClickHouse の Json カラム型をサポートしており、System.Text.Json.Nodes.JsonNode (プライマリ) または string (自動 ValueConverter 使用時) に対応付けられます。
JSON の読み書きは、SaveChanges と BulkInsertAsync の両方で利用できます:
生の JSON 文字列を使いたい場合は、プロパティを string として Json カラム型にマップしてください。プロバイダーが ValueConverter を自動的に適用します。
  • JSON パスは変換されません — LINQ の entity.Data["name"] は、ClickHouse の data.name という SQL 構文には変換されません。JSON 以外のカラムでフィルタし、JSON はメモリ上で確認してください。
  • NULL セマンティクス — ClickHouse の JSON type は、NULL 値に対して SQL NULL ではなく {} (空のオブジェクト) を返します。
  • 整数の精度 — ClickHouse の JSON は、すべての整数を Int64 として格納します。JsonNode 経由で読み取る場合は、GetValue<int>() ではなく GetValue<long>() を使用してください。

テーブルエンジン

ToTable(name, t => ...) のフルーエント API を使用して、ClickHouse テーブルのエンジンとエンジン固有の句を設定します。エンジンが設定されていない場合、プロバイダーは既定で MergeTree を使用し、ORDER BY はエンティティの主キーに基づいて決定されます。
サポートされているエンジンファミリー: エンジン句: WithOrderBy, WithPartitionBy, WithPrimaryKey, WithSampleBy, WithTtl, WithSettings。いずれも HasXxxEngine() が返すエンジンビルダーに対して指定します。 カラムレベルの機能: HasCodec, HasTtl, HasComment, HasDefault — いずれも移行の対象になります。 データスキッピング索引 — HasIndex(...).HasSkippingIndexType(...) で指定します:
標準の (スキップしない) 索引は、ClickHouse に相当するものがないため、黙って無視されます。一意索引については、ClickHouse では一意性が保証されないため、例外がスローされます。

移行

標準的な EF Core の移行ワークフロー:
サポートされている操作:

移行の制限事項

移行以外にも、このプロバイダーはまだ以下をサポートしていません。
  • UPDATE / DELETE
  • トランザクション: BeginTransaction は no-op です。ClickHouse は ACID トランザクションをサポートしていません。
  • JSON パス クエリの変換: LINQ の entity.Data["key"] は、ClickHouse の data.key SQL 構文に変換されません。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 層ラップします。

AggregateFunction カラム

AggregateFunction(...) 型のカラムは、直接クエリしたり、挿入したりすることはできません。 挿入するには:
取得するには:

最終更新日 2026年9月26日