> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-parallel-read-in-order-multi-part.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ClickHouse に接続するための公式 C# クライアントです。

# ClickHouse C# client

export const Image = ({img, alt, size = "lg", background}) => {
  const normalizedSize = ["sm", "md", "lg"].includes(size) ? size : "lg";
  const backgroundColor = background === "white" ? "white" : background === "black" ? "rgb(31 31 28)" : undefined;
  return <div className={`ch-image-${normalizedSize}`}>
      <Frame>
        <img src={img} alt={alt} style={{
    backgroundColor
  }} />
      </Frame>
    </div>;
};

ClickHouse に接続するための公式 C# クライアントです。
クライアントのソースコードは [GitHubリポジトリ](https://github.com/ClickHouse/clickhouse-cs) で公開されています。
当初は [Oleg V. Kozlyuk](https://github.com/DarkWanderer) によって開発されました。

このライブラリは、主に 2 つの API を提供します。

* **`ClickHouseClient`** (推奨) : シングルトンとしての利用を想定して設計された、高水準でスレッドセーフなクライアントです。クエリと一括挿入のためのシンプルな非同期 API を提供します。ほとんどのアプリケーションに最適です。

* **ADO.NET** (`ClickHouseDataSource`, `ClickHouseConnection`, `ClickHouseCommand`): 標準的な .NET のデータベース抽象化です。ORM インテグレーション (Dapper、Linq2db) や、ADO.NET 互換性が必要な場合に必須です。`ClickHouseBulkCopy` は、ADO.NET 接続を使用してデータを効率的に挿入するためのヘルパークラスです。`ClickHouseBulkCopy` は非推奨であり、今後のリリースで削除される予定です。代わりに `ClickHouseClient.InsertBinaryAsync` を使用してください。

どちらの API も同じ基盤となる HTTP 接続プールを共有しており、同じアプリケーション内で併用できます。

<h2 id="migration-guide">
  移行ガイド
</h2>

1. `.csproj` ファイルで、パッケージ名を新しい `ClickHouse.Driver` に変更し、[NuGet の最新バージョン](https://www.nuget.org/packages/ClickHouse.Driver) を指定します。
2. コードベース内の `ClickHouse.Client` への参照をすべて `ClickHouse.Driver` に更新します。

***

<h2 id="supported-net-versions">
  サポート対象の .NET バージョン
</h2>

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

* .NET 6.0
* .NET 8.0
* .NET 9.0
* .NET 10.0

<h2 id="supported-clickhouse-versions">
  サポートされている ClickHouse バージョン
</h2>

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

<h2 id="installation">
  インストール
</h2>

NuGet からパッケージをインストールします。

```bash theme={null}
dotnet add package ClickHouse.Driver
```

または、NuGet パッケージ マネージャーを使用します。

```bash theme={null}
Install-Package ClickHouse.Driver
```

<h2 id="quick-start">
  クイックスタート
</h2>

```csharp theme={null}
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);
```

<h2 id="configuration">
  設定
</h2>

ClickHouse への接続を設定する方法は 2 つあります。

* **接続文字列:** ホスト、認証情報、その他の接続オプションを指定する、セミコロン区切りのキーと値のペアです。
* **`ClickHouseClientSettings` object:** 設定ファイルから読み込むことも、コード内で設定することもできる、厳密に型付けされた設定オブジェクトです。

以下に、すべての設定項目、そのデフォルト値、およびそれぞれの動作への影響を一覧で示します。

<h3 id="connection-settings">
  接続設定
</h3>

| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
| - | - | - | - | - |
| 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` | 操作のタイムアウト (接続文字列では秒単位で保存されます) |

<h3 id="data-format-serialization">
  データフォーマットとシリアライゼーション
</h3>

| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
| - | - | - | - | - |
| UseCompression | `bool` | `true` | `Compression` | 通常のクエリにおける双方向の転送圧縮を制御します。サーバーに応答の圧縮を要求し (`enable_http_compression`。codec については `AcceptEncoding` を参照してください。明示的な値を指定すれば、これがオフでも要求できます)、**かつ** リクエストボディを gzip で圧縮します。ただし `UseFormDataParameters` の場合は例外で、そのマルチパートボディは常に非圧縮で送信されます。バイナリ insert ではこの設定は参照されず、`InsertOptions.Compressor` が使用されます。[Insert の圧縮](#insert-compression) を参照してください |
| AcceptEncoding | `string` | `null` | `AcceptEncoding` | すべてのリクエストとともに送信される `Accept-Encoding` で、ドライバーがデフォルトで通知する codec (`zstd, lz4, gzip, deflate`) を置き換えます。サーバーが返した内容は透過的にデコードされます。[応答の展開](#response-decompression) を参照してください |
| UseCustomDecimals | `bool` | `true` | `UseCustomDecimals` | 任意精度には `ClickHouseDecimal` を使用します。false の場合は .NET の `decimal` (128 ビット制限) を使用します |
| ReadStringsAsByteArrays | `bool` | `false` | `ReadStringsAsByteArrays` | `String` および `FixedString` カラムを `string` ではなく `byte[]` として読み取ります。バイナリデータに便利です |
| UseFormDataParameters | `bool` | `false` | `UseFormDataParameters` | パラメータを URL のクエリ文字列ではなくフォームデータとして送信します |
| ReadBufferSize | `int` | `65536` (64 KiB) | `ReadBufferSize` | HTTP クエリ応答を読み取るバッファのサイズ (バイト単位) です。ドライバーは共有プールからバッファを借り、リーダーの破棄時に返却するため、クエリごとに割り当てが発生することはありません。大きな result sets でのバッファ再補充を減らすには値を増やしてください。ドライバーは同時実行のリーダーごとに 1 つのバッファを保持するため、メモリ使用量はバッファサイズと同時実行リーダー数に応じて増加します。[バッファ](#perf-buffers) を参照してください。 |
| ParameterTypeResolver | `IParameterTypeResolver` | `null` | — | `@` スタイルのパラメータ型対応に使用するカスタムリゾルバです。[カスタムパラメータ型対応](#parameter-type-mapping) を参照してください |
| ParameterFormatter | `IParameterFormatter` | `null` | — | パラメータ値のシリアライゼーションに使用するカスタムフォーマッタです。[カスタムパラメータ値のフォーマット](#parameter-value-formatting) を参照してください |
| ReadValueConverter | `IReadValueConverter` | `null` | — | データリーダーが返す値に適用するカスタム変換です。[カスタム読み取り値変換](#read-value-conversion) を参照してください |
| JsonReadMode | `JsonReadMode` | `Binary` | `JsonReadMode` | JSON データの返却方法: `Binary` (`JsonObject` を返す) または `String` (生の JSON 文字列を返す) |
| JsonWriteMode | `JsonWriteMode` | `String` | `JsonWriteMode` | JSON データの送信方法: `String` (`JsonSerializer` 経由でシリアライズされ、すべての入力を受け付ける) または `Binary` (型ヒント付きの登録済み POCO のみ) |
| MapReadMode | `MapReadMode` | `Dictionary` | `MapReadMode` | `Map(K, V)` データの返却方法: `Dictionary` (`Dictionary<K, V>` を返す。キーが重複する場合は最後の値のみが保持されます) または `KeyValuePairs` (`List<KeyValuePair<K, V>>` を返し、すべてのペアを保持します)。[Map 型](#type-map-reading-map) を参照してください |
| AllowDuplicateJsonKeys | `bool` | `false` | `AllowDuplicateJsonKeys` | 重複するパスの双方に値を持つ `JSON` 行の読み取り方法です。`false` では例外をスローします。どちらかの値を保持することは、もう一方を破棄することを意味するためです。`true` では、その行で最後に現れた値を保持します。[重複するパス](#type-map-reading-json) を参照してください |

<h3 id="session-management">
  セッション管理
</h3>

| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
| - | - | - | - | - |
| UseSession | `bool` | `false` | `UseSession` | ステートフルなセッションを有効にし、リクエストを直列化します |
| SessionId | `string` | `null` | `SessionId` | セッション ID。null で UseSession が true の場合は、GUID が自動生成されます |

<Note>
  `UseSession` フラグを有効にすると、サーバー側セッションの状態が保持され、`SET` ステートメントや一時テーブルを利用できるようになります。セッションは 60 秒間非アクティブな状態が続くとリセットされます (デフォルトのタイムアウト) 。セッションの有効期間は、ClickHouse ステートメントまたはサーバー設定でセッション設定を指定することで延長できます。

  通常、`ClickHouseConnection` クラスでは並列動作が可能で、複数のスレッドからクエリを同時実行できます。ただし、`UseSession` フラグを有効にすると、1 つの接続で同時に実行できるアクティブなクエリは常に 1 つに制限されます (これはサーバー側の制約です) 。
</Note>

<h3 id="security">
  セキュリティ
</h3>

| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
| - | - | - | - | - |
| SkipServerCertificateValidation | `bool` | `false` | — | HTTPS 証明書の検証をスキップします。**本番環境では使用しないでください。** |

<h3 id="http-client-configuration">
  HTTP クライアントの構成
</h3>

| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
| - | - | - | - | - |
| HttpClient | `HttpClient` | `null` | — | カスタムの事前構成済み HttpClient インスタンス |
| HttpClientFactory | `IHttpClientFactory` | `null` | — | HttpClient インスタンスを作成するためのカスタム ファクトリ |
| HttpClientName | `string` | `null` | — | HttpClientFactory で特定のクライアントを作成する際の名前 |

<h3 id="logging-debugging">
  ログとデバッグ
</h3>

| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
| - | - | - | - | - |
| LoggerFactory | `ILoggerFactory` | `null` | — | 診断ログ用のロガーファクトリ |
| EnableDebugMode | `bool` | `false` | — | .NET のネットワーク トレースを有効にします (Trace レベルに設定された LoggerFactory が必要) 。**パフォーマンスへの影響が大きい** |

<h3 id="custom-settings-roles">
  カスタム設定とロール
</h3>

| プロパティ | 型 | デフォルト | 接続文字列キー | 説明 |
| - | - | - | - | - |
| CustomSettings | `IDictionary<string, object>` | 空 | `set_*` プレフィックス | ClickHouse のサーバー設定。以下の注記を参照してください |
| Roles | `IReadOnlyList<string>` | 空 | `Roles` | カンマ区切りの ClickHouse ロール (例: `Roles=admin,reader`) |
| ApplicationInfo | `IReadOnlyDictionary<string, string>` | 空 | — | アプリケーションごとのクエリの帰属先を識別するため、HTTP `User-Agent` ヘッダーに追加される自由形式のタグ。 |

<Note>
  接続文字列でカスタム設定を指定する場合は、`set_` プレフィックスを使用します。たとえば `"set_max_threads=4"` のように指定します。ClickHouseClientSettings オブジェクトを使用する場合は、`set_` プレフィックスは使用しません。

  使用可能な設定の一覧については、[こちら](/ja/reference/settings/session-settings)を参照してください。
</Note>

***

<h3 id="connection-string-examples">
  接続文字列の例
</h3>

<h4 id="basic-connection">
  基本接続
</h4>

```text theme={null}
Host=localhost;Port=8123;Username=default;Password=secret;Database=mydb
```

<h4 id="with-custom-clickhouse-settings">
  カスタムのClickHouse設定を使用する場合
</h4>

```text theme={null}
Host=localhost;set_max_threads=4;set_readonly=1;set_max_memory_usage=10000000000
```

***

<h3 id="query-options">
  QueryOptions
</h3>

`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` | `@` 形式のパラメータ型対応に対するクライアント レベルのリゾルバを上書きします。詳細は [カスタム パラメータ型対応](#parameter-type-mapping) を参照してください |
| ParameterFormatter | `IParameterFormatter` | `@` 形式のパラメータ値のシリアライゼーションに対するクライアント レベルのフォーマッタを上書きします。詳細は [カスタム パラメータ値のフォーマット](#parameter-value-formatting) を参照してください |
| ReadValueConverter | `IReadValueConverter` | データ リーダーから返される値に適用されるクライアント レベルの変換を上書きします。詳細は [カスタム読み取り値変換](#read-value-conversion) を参照してください |
| MaxExecutionTime | `TimeSpan?` | サーバー側のクエリ タイムアウト (`max_execution_time` 設定として渡されます) 。制限を超えると、サーバーがクエリをキャンセルします |
| AcceptEncoding | `string` | クエリごとの `Accept-Encoding` の上書き (例: `"br"`、`"identity"`) 。`ClickHouseClientSettings.AcceptEncoding` よりも優先されます。また、URL で `enable_http_compression=1` も強制します。詳細は [クエリごとの転送圧縮](#per-query-accept-encoding) を参照してください。 |

**例:**

```csharp theme={null}
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
);
```

***

<h3 id="insert-options">
  InsertOptions
</h3>

`InsertOptions` は、`InsertBinaryAsync` による一括挿入操作に固有の設定を追加して、`QueryOptions` を拡張したものです。

| プロパティ | 型 | デフォルト | 説明 |
| - | - | - | - |
| BatchSize | `int` | 100,000 | Batch ごとの行数 |
| MaxDegreeOfParallelism | `int` | 1 | 並列 Batch アップロード数 |
| Format | `RowBinaryFormat` | `RowBinary` | バイナリフォーマット: `RowBinary` または `RowBinaryWithDefaults` |
| Compressor | `IClickHouseCompressor` | `ZstdCompressor.Default` | insert ボディ (`Content-Encoding`) に適用される codec。`null` の場合は非圧縮で送信します。[Insert の圧縮](#insert-compression) を参照してください |
| QueryPlacement | `InsertQueryPlacement` | `Body` | `INSERT INTO ... FORMAT ...` ステートメントの送信先: `Body` (行の前) または `Url` (`query` URL パラメータとして)。[INSERT クエリの配置](#insert-query-placement) を参照してください |
| ColumnTypes | `IReadOnlyDictionary<string, string>` | `null` | カラム名 → ClickHouse 型文字列。設定すると、スキーマプローブクエリをスキップします。 |
| UseSchemaCache | `bool` | `false` | クライアントのライフタイム中、(database, table) ごとに完全なテーブルスキーマを cache します。 |

`QueryOptions` のすべてのプロパティは、`InsertOptions` でも利用できます。

**例:**

```csharp theme={null}
var insertOptions = new InsertOptions
{
    BatchSize = 50_000,
    MaxDegreeOfParallelism = 4,
    QueryId = "bulk-import-001"
};

long rowsInserted = await client.InsertBinaryAsync(
    "my_table",
    columns,
    rows,
    insertOptions
);
```

<h4 id="skip-schema-query">
  スキーマプローブクエリのスキップ
</h4>

デフォルトでは、`InsertBinaryAsync` は各 insert の前に `SELECT ... WHERE 1=0` クエリを送信し、カラムの型を特定します。高スループットが求められる場合は、2 つの方法でこのオーバーヘッドをなくせます。

**オプション 1: カラム型を明示的に指定する**

コンパイル時点でテーブルのスキーマがわかっている場合は、`ColumnTypes` で直接指定します。これにより、スキーマプローブクエリは一切送信されません。

```csharp theme={null}
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 に再利用されます:

```csharp theme={null}
var options = new InsertOptions { UseSchemaCache = true };

// 最初の呼び出しでサーバーからスキーマを取得する
await client.InsertBinaryAsync("my_table", columns, batch1, options);

// 2回目の呼び出しではキャッシュ済みスキーマを再利用する — 追加のラウンドトリップなし
await client.InsertBinaryAsync("my_table", columns, batch2, options);
```

<Note>
  * `ColumnTypes` は `UseSchemaCache` より優先されます。両方が設定されている場合は、明示的に指定した型が使用されます。
  * スキーマ cache では `ALTER TABLE` による変更は検出されません。テーブルのスキーマを変更した場合は、新しい `ClickHouseClient` を作成するか、そのテーブルでは `UseSchemaCache` を使用しないでください。
  * cache は `ClickHouseClient` インスタンス単位で管理され、キーは (database, table) です。同じテーブル上の異なるカラムのサブセットでは、1 つのキャッシュ済みスキーマが共有されます。
</Note>

<h2 id="clickhouse-client">
  ClickHouseClient
</h2>

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

<h3 id="creating-a-client">
  クライアントの作成
</h3>

接続文字列または `ClickHouseClientSettings` オブジェクトを使用して、`ClickHouseClient` を作成します。利用可能なオプションについては、[設定](#configuration) セクションを参照してください。

ClickHouse Cloud サービスの詳細は、ClickHouse Cloud コンソールで確認できます。

サービスを選択し、**Connect** をクリックします。

<Image img="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/GTkpPcjoRQ_okrH3/images/_snippets/cloud-connect-button.webp?fit=max&auto=format&n=GTkpPcjoRQ_okrH3&q=85&s=d059c1bbcc7317ff8df85b20189e65f4" size="md" alt="ClickHouse Cloud service connect button" border width="998" height="932" data-path="images/_snippets/cloud-connect-button.webp" />

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

<Image img="https://mintcdn.com/private-7c7dfe99-parallel-read-in-order-multi-part/GTkpPcjoRQ_okrH3/images/_snippets/connection-details-csharp.webp?fit=max&auto=format&n=GTkpPcjoRQ_okrH3&q=85&s=487b14816a8a8711d46ae022d82d74ef" size="md" alt="ClickHouse Cloud C# connection details" border width="851" height="805" data-path="images/_snippets/connection-details-csharp.webp" />

セルフマネージドの ClickHouse を使用している場合、接続情報は ClickHouse 管理者が設定します。

接続文字列を使用する場合:

```csharp theme={null}
using ClickHouse.Driver;

using var client = new ClickHouseClient("Host=localhost;Username=default;Password=secret");
```

または、`ClickHouseClientSettings` を使用します。

```csharp theme={null}
using ClickHouse.Driver;

var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    Username = "default",
    Password = "secret"
};
using var client = new ClickHouseClient(settings);
```

依存関係の注入のシナリオでは、`IHttpClientFactory` を使用します。

```csharp theme={null}
// In your DI configuration. No AutomaticDecompression needed — the driver decodes
// compressed responses itself, and a mask here would widen its Accept-Encoding.
services.AddHttpClient("ClickHouse", client =>
{
    client.Timeout = TimeSpan.FromMinutes(5);
});

// Create client with factory
var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var client = new ClickHouseClient("Host=localhost", factory, "ClickHouse");
```

<Note>
  `ClickHouseClient` は長期間の利用を前提としており、アプリケーション全体で共有して使うように設計されています。一度だけ作成し (通常はシングルトンとして) 、すべてのデータベース操作で再利用してください。クライアントは内部で HTTP 接続プーリングを管理します。
</Note>

***

<h3 id="executing-queries">
  クエリの実行
</h3>

結果を返さないステートメントには `ExecuteNonQueryAsync` を使用します:

```csharp theme={null}
// テーブルを作成する
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` を使用します：

```csharp theme={null}
var count = await client.ExecuteScalarAsync("SELECT count() FROM default.my_table");
Console.WriteLine($"行数: {count}");

var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine($"サーバーバージョン: {version}");
```

***

<h3 id="inserting-data">
  データの挿入
</h3>

<h4 id="parameterized-inserts">
  パラメーター化された挿入
</h4>

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

```csharp theme={null}
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
);
```

***

<h4 id="bulk-insert">
  一括挿入
</h4>

大量の行を効率よく挿入するには、`InsertBinaryAsync` を使用します。これは ClickHouse のネイティブな行バイナリ形式でデータをストリーミングし、バッチの並列アップロードをサポートするとともに、パラメーター化クエリで発生することがある「URL が長すぎる」エラーを回避します。

```csharp theme={null}
// 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` を使用してバッチ処理と並列度を設定します:

```csharp theme={null}
var options = new InsertOptions
{
    BatchSize = 100_000,           // バッチあたりの行数（デフォルト: 100,000）
    MaxDegreeOfParallelism = 4     // 並列バッチアップロード数（デフォルト: 1）
};
```

<Note>
  * クライアントは挿入前に `SELECT * FROM <table> WHERE 1=0` を実行して、テーブル構造を自動的に取得します。指定する値は、対象カラムの型と一致している必要があります。このクエリを省略するには、[`InsertOptions.ColumnTypes` または `InsertOptions.UseSchemaCache`](#skip-schema-query) を使用してください。
  * `MaxDegreeOfParallelism > 1` の場合、バッチは並列にアップロードされます。セッションは並列挿入に対応していないため、セッションを無効にするか、`MaxDegreeOfParallelism = 1` に設定してください。
  * 指定していないカラムに対してサーバーが DEFAULT 値を適用するようにするには、`InsertOptions.Format` で `RowBinaryFormat.RowBinaryWithDefaults` を使用してください。
</Note>

<h4 id="poco-insert">
  POCO の挿入
</h4>

`object[]` 配列を組み立てる代わりに、厳密に型付けされた POCO オブジェクトを直接 insert できます。型を一度登録したら、あとは `IEnumerable<T>` を渡します:

```csharp theme={null}
// テーブルのカラムに対応する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);
```

既定では、公開されているすべての読み取り可能なプロパティは、厳密な大文字と小文字の区別を伴う名前一致によりカラムにマッピングされます。属性を使用して、このマッピングをカスタマイズできます。

```csharp theme={null}
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` (バッチ化、並列度、スキーマキャッシュ) をサポートします。

<Note>
  `object[]` オーバーロードとは異なり、`InsertBinaryAsync<T>` では明示的なカラムリストを指定できません。カラムは、登録された型のマップ済みプロパティに基づいて決定されます。挿入するカラムを制御するには、`[ClickHouseNotMapped]` を使ってプロパティを除外するか、`[ClickHouseColumn(Name = "...")]` を使って名前を変更します。

  `InsertOptions` で `ColumnTypes` が設定されている場合は、POCO 属性よりそちらが優先されます。
</Note>

<h4 id="poco-insert-schema-evolution">
  スキーマ進化
</h4>

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

<h4 id="insert-query-placement">
  INSERT クエリの配置
</h4>

バイナリ挿入では、`INSERT INTO ... FORMAT ...` ステートメントが行データの前、リクエストボディの先頭行として書き込まれます。ボディはデフォルトで圧縮されるため、URL のみを検査するルーティングやログからはこのステートメントが見えません。`InsertOptions.QueryPlacement` に `InsertQueryPlacement.Url` を設定すると、ステートメントは代わりに `query` URL パラメータとして送信され、ボディには行データのみが残ります:

```csharp theme={null}
var options = new InsertOptions { QueryPlacement = InsertQueryPlacement.Url };
await client.InsertBinaryAsync("events", columns, rows, options);
```

プロキシ、ロードバランサー、ゲートウェイが `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` とは独立しています。ボディはどちらのモードでも同じ方法でエンコードされます。

***

<h3 id="reading-data">
  データの読み取り
</h3>

SELECT クエリの実行には `ExecuteReaderAsync` を使用します。返される `ClickHouseDataReader` では、`GetInt64()`、`GetString()`、`GetFieldValue<T>()` などのメソッドを使って、結果カラムに型付きでアクセスできます。

次の行に進むには `Read()` を呼び出します。これ以上行がない場合は `false` を返します。カラムには、インデックス (0 始まり) またはカラム名でアクセスできます。

```csharp theme={null}
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)}");
}
```

<h4 id="poco-read">
  POCO の読み取り
</h4>

カラムをインデックスや名前で読み取る代わりに、クエリ結果を独自のクラスへ直接ストリームできます。型をクライアントに一度登録すれば、`QueryAsync<T>` を使用できます。

```csharp theme={null}
// 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}");
}
```

<h5 id="poco-read-registration">
  登録
</h5>

`RegisterPocoType<T>()` は、insert と read の両方のマッピングを設定し、両方を事前に検証します。`RegisterBinaryInsertType<T>()` に変更はなく、backwards compatibility のため引き続き insert 専用です。

登録する型は、次の条件を満たしている必要があります。

* public の引数なしコンストラクター
* public で `init` ではない setter を持つ public プロパティが少なくとも 1 つあること。`required` プロパティもサポートされます。

<h5 id="poco-read-column-matching">
  カラムのマッチング
</h5>

カラムのマッチングでは大文字と小文字が区別されます。結果に存在しないカラムについては、対応するプロパティはデフォルト値のままとなり、余分な結果カラムは無視されます。

ドライバーは値の拡大変換や縮小変換を行いません。以下に挙げる代替表現を除き、カラムのフレームワーク型はプロパティの型に代入可能である必要があり、一致しない場合は `InvalidOperationException` がスローされます。したがって、`object` 型のプロパティは任意のカラムを受け入れます。

<h5 id="poco-read-types">
  サポートされるプロパティ型
</h5>

`QueryAsync<T>` は、以下の各カラムを対応するプロパティへ直接読み込みます:

| ClickHouse カラム | プロパティ型 |
| - | - |
| `Int8`/`Int16`/`Int32`/`Int64` | `sbyte`/`short`/`int`/`long` |
| `UInt8`/`UInt16`/`UInt32`/`UInt64` | `byte`/`ushort`/`uint`/`ulong` |
| `Int128`/`UInt128` | `BigInteger`、または .NET 8 以降ではネイティブの `System.Int128`/`System.UInt128` |
| `Int256`/`UInt256` | `BigInteger` |
| `Float32`/`Float64`/`BFloat16` | `float`/`double`/`float` |
| `Bool` | `bool` |
| `Decimal` | `decimal` または `ClickHouseDecimal` |
| `Date`/`Date32`/`DateTime`/`DateTime64` | `DateTime`、`DateTimeOffset` または `DateOnly` |
| `Time`/`Time64` | `TimeSpan` |
| `UUID` | `Guid` |
| `IPv4`/`IPv6` | `IPAddress` |
| `Enum8`/`Enum16` | `string`(ラベル)または `int`(ワイヤ上の序数) |
| `String`/`FixedString` | `string` または `byte[]` |

いずれの行でも、カラムが `Nullable(...)` であるかどうかにかかわらず、そのプロパティ型の nullable 形式(`long?`、`DateOnly?` など)を使用できます。`Nullable(T)` カラムに対して null 非許容の値型プロパティを指定した場合、登録時には受け付けられますが、NULL が到着した時点で例外がスローされます。

`LowCardinality(T)`、`SimpleAggregateFunction(f, T)`、`Object(T)` といったラッパーは、`T` とまったく同じようにマッピングされます。

複合型のカラムもサポートされており、[読み取り時の型リファレンス](#clickhouse-native-type-map-reading)に記載されたフレームワーク型が使用されます。すなわち、`Array(T)` は `T[]`、`Tuple(...)` は `System.Tuple<...>`、`Nested(...)` は `Tuple<...>[]`、`JSON` は `JsonObject`([`JsonReadMode=String`](#type-map-reading-json) では `string`)、`Variant`/`Dynamic` は `object` となります。

`Map(K, V)` カラムは特別なケースです。`List<KeyValuePair<K, V>>` または `KeyValuePair<K, V>[]` のプロパティはボックス化を伴わない経路で読み取られ、いずれの [`MapReadMode`](#type-map-reading-map) でもワイヤ上の順序と重複するキーをそのまま保持します。`Dictionary<K, V>` プロパティはデフォルトモードでのみ利用できます。キーと値の型は厳密に一致している必要があるため、`Map(String, Nullable(Int32))` には `KeyValuePair<string, int?>` が必要です。

1 つのカラムに複数のプロパティ型が用意されている場合(`DateTime` カラムに対する `DateTime`、`DateTimeOffset`、`DateOnly`、`String` カラムに対する `string` または `byte[]` など)、宣言したプロパティ型によって表現が決まります。これらの代替表現は POCO 経路に属するものであるため、`QueryAsync<T>` では利用できますが、`MapTo<T>` では利用できません。

<h5 id="poco-read-mapto">
  1 行をマテリアライズする
</h5>

リーダーを手動で順に処理する場合は、`ClickHouseDataReader.MapTo<T>()` を使用して、リーダーを進めずに現在の行を登録済みの POCO にマテリアライズします。

```csharp theme={null}
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}");
}
```

リーダーのループを自分で制御する必要がある場合、たとえば生のカラムアクセスと POCO のマテリアライズを組み合わせたい場合には、`MapTo<T>` を使用します。これはリーダーのボックス化された値を通じて行を読み取るため、上記の代替プロパティ型には対応しておらず、`QueryAsync<T>` よりも多くのアロケーションが発生します。行だけが必要な場合は `QueryAsync<T>` を使用してください。具体的な数値については [マテリアライズ方式の選択](#perf-read-path) を参照してください。

<h5 id="poco-read-converters">
  読み取り値コンバーター
</h5>

クライアントレベルまたはクエリ単位の[読み取り値コンバーター](#read-value-conversion)は両方のパスに適用され、
ボックス化なしの読み取りを無効化することはありません。ドライバーは、各カラムをそのカラムの読み取り方法に対応する
オーバーロードで変換します。すなわち、ボックス化なしのカラムには型付きの `ConvertValue<T>` を、
複合型のカラムにはボックス化された `ConvertValue` を使用します。この2つのオーバーロードは一貫性を保って
実装してください。そうしないと、同じカラムであってもパスによって結果が異なってしまいます。

<h5 id="poco-read-diagnostics">
  登録時の診断情報
</h5>

`LoggerFactory` が設定されている場合、`RegisterPocoType<T>()` と `RegisterBinaryInsertType<T>()` は、どのプロパティがどのカラムにマッピングされたか、またどのプロパティがなぜスキップされたのかを示す `Debug` レベルのログ (カテゴリ `ClickHouse.Driver.Client`) を出力します。詳しくは、[ロギングと診断情報](#logging-and-diagnostics)を参照してください。

***

<h3 id="sql-parameters">
  SQLパラメータ
</h3>

ClickHouse では、SQLクエリのクエリパラメータの標準的なフォーマットは `{parameter_name:DataType}` です。

**例:**

```sql theme={null}
SELECT {value:Array(UInt16)} as a
```

```sql theme={null}
SELECT * FROM table WHERE val = {tuple_in_tuple:Tuple(UInt8, Tuple(String, UInt8))}
```

```sql theme={null}
INSERT INTO table VALUES ({val1:Int32}, {val2:Array(UInt8)})
```

<Note>
  SQL の'bind' パラメータは HTTP URI のクエリパラメータとして渡されるため、数が多すぎると "URL が長すぎる" 例外が発生することがあります。この制限を回避してデータを一括挿入するには、`InsertBinaryAsync` を使用してください。
</Note>

<h4 id="at-style-placeholders">
  ADO 形式の `@name` プレースホルダー
</h4>

ドライバーは、Dapper などの ORM が出力する `@name` プレースホルダーも受け付けます。これはクライアント側の利便性のための機能で、リクエスト送信前に各プレースホルダーが `{name:ResolvedType}` へ書き換えられるため、サーバー側に `@` が渡ることはありません。型の選択方法については [型解決](#parameter-type-mapping) を参照してください。可能な限り、明示的な `{name:Type}` 形式を使用してください。

対応するパラメーターが存在しない `@name` はそのまま残され、サーバー側で拒否されます。マッチングでは大文字と小文字が区別されるため、`@ID` は `id` という名前のパラメーターにはバインドされません。

<Note>
  この書き換えを無効にするには、ドライバーの初回使用前に `ClickHouse.Driver.DisableReplacingParameters` AppContext スイッチを設定してください。停止するのはテキストの書き換えのみで、パラメーター自体は引き続き送信されるため、ネイティブな `{name:Type}` 構文で記述されたクエリはそのまま動作します。
</Note>

<h4 id="identifier-parameters">
  Identifier パラメーター
</h4>

`Identifier` パラメーター型を使用すると、引用符付きの文字列リテラルの代わりに、データベース名、テーブル名、またはカラム名を安全にバインドできます。SQL では `{name:Identifier}` 構文を使用するか、`ClickHouseDbParameter.ClickHouseType = "Identifier"` を設定して使用します。

```csharp theme={null}
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("name", "my_database");

await client.ExecuteNonQueryAsync("CREATE DATABASE {name:Identifier}", parameters);
```

```csharp theme={null}
var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("col", "user_id");

var reader = await client.ExecuteReaderAsync("SELECT {col:Identifier} FROM t", parameters);
```

値はそのまま送信され、サーバーがそれをクォートなしのSQL識別子として置き換え、サーバー側のバッククォートによる引用とエスケープを適用します。特殊文字 (バッククォートを含む) を含む識別子も、安全にラウンドトリップできます。

***

<h3 id="query-id">
  クエリ ID
</h3>

すべてのクエリには一意の `query_id` が割り当てられます。これは、`system.query_log` テーブルからデータを取得したり、長時間実行中のクエリをキャンセルしたりする際に使用できます。`QueryOptions` でカスタムのクエリ ID を指定することもできます。

```csharp theme={null}
var options = new QueryOptions
{
    QueryId = $"report-{Guid.NewGuid()}"
};

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM large_table",
    parameters: null,
    options: options
);
```

<Tip>
  カスタムの `QueryId` を指定する場合は、呼び出しごとに必ず一意になるようにしてください。ランダムな GUID を使うのが適切です。
</Tip>

***

<h3 id="parameter-type-mapping">
  カスタム パラメータ型マッピング
</h3>

`@` 形式のパラメータ (例: `WHERE id = @id`) を使用すると、ドライバーは .NET の値型から ClickHouse の型を自動的に推論します。たとえば、`int` は `Int32` にマッピングされます。

<Warning>
  **推論される DateTime パラメータの挙動**

  SQL に `{name:Type}` ヒントがなく、`ClickHouseType` も設定されていない `@` 形式のパラメータでは、時点を表す値は単なる `DateTime` ではなく `DateTime('UTC')` として推論されます。`Kind` が `Utc` または `Local` の `DateTime` と、すべての `DateTimeOffset` の値は `DateTime('UTC')` として送信されるため、どのサーバータイムゾーンでも同じ時点が保持されます。

  明示的なヒント (`{name:DateTime}`) は推論より優先され、クエリを構築する推奨方法です。
</Warning>

これらの既定の対応を上書きするには、`ClickHouseClientSettings` で `ParameterTypeResolver` を設定します。これは、個々のパラメータごとに `ClickHouseType` を設定しなくても、すべての `DateTime` パラメータでミリ秒精度の `DateTime64(3)` を使いたい場合や、すべての `decimal` で特定の小数点以下桁数を使いたい場合に便利です。

**シンプルな型マッピングに `DictionaryParameterTypeResolver` を使用する:**

```csharp theme={null}
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` を返します。

```csharp theme={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` を介してリゾルバを設定することもできます。設定した場合、クライアントレベルのリゾルバより優先されます。

**型解決の優先順位:**

リゾルバは優先順位チェーンの一要素です。優先度の高いものから低いものの順に示すと、次のとおりです。

1. パラメータに明示的に設定された `ClickHouseType`
2. クエリ内の `{name:Type}` 構文による SQL の型ヒント
3. `IParameterTypeResolver` (`QueryOptions.ParameterTypeResolver` を使用し、未設定の場合は `ClickHouseClientSettings.ParameterTypeResolver` にフォールバック)
4. 組み込みの型推論 (`TypeConverter.ToClickHouseType`)

このリゾルバは、ADO.NET の `ClickHouseConnection` パスでも機能します。設定は、クライアントから作成された接続に引き継がれます。

***

<h3 id="parameter-value-formatting">
  カスタム パラメータ値のフォーマット
</h3>

`IParameterFormatter` は、パラメータ値をどのようにシリアライズするかを決定するフックです。組み込みのフォーマット (例: DateTime の精度、小数のカルチャ、文字列のエスケープ、数値表現) が、スキーマや後続のツールの想定と一致しない場合に使用します。

パラメータ化されたすべてのクエリにフォーマッタを適用するには、`ClickHouseClientSettings` で `ParameterFormatter` を設定します。このフォーマッタは、値、解決された ClickHouse の型名、パラメータ名を受け取り、サーバーに送信される文字列表現を返します。既定のフォーマッタに処理を委ねるには、`null` を返します。

**単純な CLR 型ごとのフォーマットには `DictionaryParameterFormatter` を使用します:**

```csharp theme={null}
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`:**

```csharp theme={null}
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 は不要です。

**フォーマッタの優先順位:**

1. `IParameterFormatter` (`QueryOptions.ParameterFormatter` を使用し、未設定の場合は `ClickHouseClientSettings.ParameterFormatter` にフォールバック) 。これが non-null を返した場合は、その値が使用されます。
2. `HttpParameterFormatter` に組み込まれている type 固有のフォーマット。

このフォーマッタは `null` または `DBNull` の値には適用されません。これらは常に ClickHouse の null センチネル (`\N`) として serialize されます。

***

<h3 id="read-value-conversion">
  カスタム読み取り値変換
</h3>

`IReadValueConverter` を使用すると、CLR 型を変更せずに、データリーダーが返す値をデシリアライゼーション後に変換できます。一般的な用途としては、タイムゾーンを持たない `DateTime` カラムに対して `DateTime.Kind = Utc` を設定すること、文字列のトリミングや正規化を行うこと、あるいは JSON カラムがアプリケーションコードに渡される前に後処理することなどがあります。

すべての読み取りに対してコンバーターを適用するには、`ClickHouseClientSettings` で `ReadValueConverter` を設定します。コンバーターは、ボックス化された (`GetValue`) パスとジェネリック (`GetFieldValue<T>`) パスの両方で、各カラムの各行ごとに 1 回呼び出されます。コンバーターが設定されていない場合、オーバーヘッドはゼロで、リーダーは値をそのまま返します。

**単純な CLR 型単位の変換に `DictionaryReadValueConverter` を使用する:**

```csharp theme={null}
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` を直接実装してください:

```csharp theme={null}
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` です。

**どちらのオーバーロードが実行されるか:**

ドライバーがどちらを呼び出すかは、呼び出し元がカラムをどのように読み取ったかによって決まるため、両方のオーバーロードは一致している必要があります:

* `ConvertValue<T>` — 型付きアクセサである `GetByte`, `GetSByte`, `GetInt16`/`32`/`64`,
  `GetUInt16`/`32`/`64`, `GetFloat`, `GetDouble`, `GetGuid`, `GetDateTime`, `GetIPAddress`,
  `GetBigInteger` および `GetFieldValue<T>`、さらに
  [POCO 読み取りパス](#poco-read-converters)におけるボックス化を伴わないすべてのカラム。
* `ConvertValue` (ボックス化) — `GetValue`, `GetValues`, インデクサー, `GetChar`, `GetTuple`、および
  `GetBoolean`, `GetDecimal`, `GetString` における型強制を伴うパス。

`IsDBNull` はコンバーターをまったく実行しません。null フラグを直接読み取るため、コンバーターによって
値が null と見なされるかどうかが変わることはありません。`TryGetEnumOrdinal` も同様にコンバーターを
迂回します — [enum の序数の読み取り](#ado-net-reader-enum-ordinal)を参照してください。

このコンバーターは、ADO.NET の `ClickHouseConnection` 経由のパスで動作します。設定は、クライアントから作成される接続に引き継がれます。

***

<h3 id="raw-streaming">
  生データのストリーミング
</h3>

データリーダーを介さず、特定のフォーマットでクエリ結果を直接ストリーミングするには、`ExecuteRawResultAsync` を使用します。これは、データをファイルにエクスポートしたり、他のシステムにそのまま渡したりする場合に便利です：

```csharp theme={null}
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`。利用可能なオプションについては、[フォーマットのドキュメント](/ja/reference/formats/index)を参照してください。

***

<h3 id="per-query-accept-encoding">
  クエリごとの転送圧縮
</h3>

デフォルトでは、`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` を受け入れる前に必要とする条件です) 。

```csharp theme={null}
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();
```

<h4 id="per-query-accept-encoding-httpclient">
  HttpClient configuration
</h4>

設定は不要です。ドライバーが構築する `HttpClient` は `AutomaticDecompression` を `DecompressionMethods.None` のままにし、レスポンスのデコードはドライバー自身が行います。そのため `Content-Encoding` が知らないうちに取り除かれることはなく、生のボディがサーバーの送信したままの形で手元に届きます。

<Warning>
  独自の `HttpClient` を渡す場合も、`AutomaticDecompression` は無効のままにしてください。これはレスポンス側だけの設定ではありません。送信時に、ハンドラーは**自身のマスクに含まれるアルゴリズムのうち、送出される `Accept-Encoding` に含まれていないものをすべて追加します**。そのため `GZip | Deflate` を持つハンドラーは、明示的に指定した `AcceptEncoding = "lz4"` を `lz4, gzip, deflate` に、明示的な `"identity"` を `identity, gzip, deflate` に、実際の通信上では書き換えてしまいます。さらに ClickHouse はこのヘッダーを独自の固定的なコーデック優先順位で解決する (順序や q 値は無視する) ため、まったく要求していないコーデックで応答することがあり、それをハンドラーがデコードして取り除いてしまうため、何が起きたのかを知ることすらできません。マスクを無効にしておけば、提示する内容は指定したとおりに保たれます。
</Warning>

<Warning>
  `AcceptEncoding` でドライバーがデコードできないコーデック (`snappy`) を要求した場合、安全なのは `ExecuteRawResultAsync` のみです。`ExecuteReaderAsync`、`ExecuteScalarAsync`、`ExecuteNonQueryAsync` は、そのコーデック名を含む `NotSupportedException` で失敗します (以前は圧縮バイト数をそのまま結果フォーマットとして parse し、意味をなさないデータを生成していました) 。
</Warning>

<h4 id="per-query-accept-encoding-errors">
  エラーのボディ
</h4>

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

***

<h3 id="response-decompression">
  レスポンスの解凍
</h3>

`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` を自分で指定します。クライアント全体に適用する場合:

```csharp theme={null}
using var client = new ClickHouseClient(new ClickHouseClientSettings("Host=localhost")
{
    AcceptEncoding = "br",      // decodable, but not advertised by default
});
```

クエリごとに設定でき、そちらが優先されます。

```csharp theme={null}
using var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM events",
    options: new QueryOptions { AcceptEncoding = "identity" });   // opt this query out
```

または、`ClickHouseClientSettings` を直接扱わない ORM 利用者向けに、接続文字列で指定することもできます:

```text theme={null}
Host=localhost;AcceptEncoding=br, gzip
```

これを設定すると、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) によって変わります。[圧縮のチューニング](#tuning-compression)を参照してください。

* **`http_zlib_compression_level`。** この設定はすべての HTTP コーデックに適用され、デフォルト値は 3 です。この値は、データ、回線速度、CPU 使用率に応じてチューニングしてください。
* **高速な回線における CPU-bound なクライアント。** ドライバーは呼び出し元の thread 上で response body をデコードするため、ネットワークが bottleneck でない場合は、client-side のデコード速度が制限要因になり得ます。

以下のいずれかに該当する場合は、クエリ単位またはクライアント全体で別のコーデックを要求してください:

```csharp theme={null}
using var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM events",
    options: new QueryOptions { AcceptEncoding = "lz4" });   // decode this one with lz4 instead
```

判断はレスポンスに基づいて行われるため、リクエスト時に何を指定したかに関わらず、`Content-Encoding` がそう示していればボディはデコードされます。ヘッダーが存在しない場合や `identity` の場合はそのまま素通しされ、サポートされているコーデックであればデコードされ、それ以外の場合はその名前を含む error が送出されます。二重デコードのおそれはありません。caller が指定した handler の `AutomaticDecompression` がすでにボディをデコード済みの場合、`Content-Encoding` も併せて取り除かれるため、ドライバーからは平文として見え、そのまま何も行われません。

**生データの結果はコーデックを通知しません。** `ExecuteRawResultAsync` (および公開 API の `PostStreamAsync` / `InsertRawStreamAsync`) は、ボディをそのまま呼び出し側に渡します。そのため、自分でコーデックを指定しない限り、これらはコーデックを一切要求しません。ドライバー側にこうしたボディをデコードする仕組みはないため、ここでコーデックを提示すると、エクスポート結果が知らないうちに圧縮ファイルになってしまいます。したがってルールは単純で、`HttpClient` の構成にも左右されません。すなわち、**そのまま渡されるボディはサーバーが送信した内容そのままで届き、サーバーはコーデックを要求されない限り平文を送信します。** コーデックを要求すること (クライアント全体またはクエリ単位) が、意図的に圧縮バイト数をエクスポートする手段となります。

明示的な `AcceptEncoding` (いずれのレベルでも) は生データのリクエストにも適用され、デコードが必要な場合は `ClickHouseRawResult.ReadDecompressedStreamAsync()` が結果をデコードします。`ReadAsStreamAsync`、`ReadAsByteArrayAsync`、`ReadAsStringAsync`、`CopyToAsync` は常に、届いたバイト列をそのまま返します。

```csharp theme={null}
using var result = await client.ExecuteRawResultAsync(
    "SELECT * FROM events FORMAT JSONEachRow",
    options: new QueryOptions { AcceptEncoding = "lz4" });

Console.WriteLine(result.ContentEncoding); // "lz4"

await using var body = await result.ReadDecompressedStreamAsync();
using var bodyReader = new StreamReader(body);
var json = await bodyReader.ReadToEndAsync();
```

上記のとおり、返されたストリームはスコープを外れる前に最後まで読み切ってください。レスポンスが圧縮されている**場合**は、`leaveOpen` で作成されたデコーダが返されるため、これを破棄してもレスポンス自体は保持されます。圧縮されて**いない**場合は HTTP コンテンツストリームそのものが返されるため、これを破棄するとボディは終了します。いずれの場合も `ClickHouseRawResult` がレスポンスを所有しています。ストリームを破棄した後は、その他の読み取りメンバーを呼び出さないでください。`ClickHouseRawResult` の破棄は常に必須であり、それだけで十分です。レスポンスと、ここで挿入されたデコーダの両方を解放します (デコーダはプールされたバッファを保持しています) 。したがって、上記の `await using` は任意ですが、記述しておいても問題ありません。連続して繰り返し呼び出すと同じストリームが返されます。この型は同時実行での使用には対応していません。

実行可能なサンプルは [Select\_007\_ResponseCompression.cs](https://github.com/ClickHouse/clickhouse-cs/blob/main/examples/Select/Select_007_ResponseCompression.cs) を参照してください。

<h4 id="insert-compression">
  Insert (リクエスト) 圧縮
</h4>

insert のデフォルトコーデックは Zstd です。`InsertOptions.Compressor` の初期値は `ZstdCompressor.Default` (レベル 3 の zstd) です。コーデックを変更するには別の compressor を指定し、ボディを非圧縮で送信するには `null` を指定します。

```csharp theme={null}
var options = new InsertOptions { Compressor = GZipCompressor.Default };  // Content-Encoding: gzip
await client.InsertBinaryAsync("events", columns, rows, options);
```

ドライバーには4つのコーデックが同梱されています。それぞれに `Default` インスタンスと、レベルおよび書き込みバッファのサイズを受け取るコンストラクターが用意されています。

| Compressor | `Content-Encoding` | コンストラクター | `Default` |
| - | - | - | - |
| `ZstdCompressor` | `zstd` | `(int level = 3, int bufferSize = 262144)` | レベル 3 |
| `Lz4Compressor` | `lz4` | `(Lz4Level level = Lz4Level.Fast, int bufferSize = 262144)` | `Lz4Level.Fast` |
| `GZipCompressor` | `gzip` | `(CompressionLevel level = CompressionLevel.Fastest, int bufferSize = 262144)` | `Fastest` |
| `BrotliCompressor` | `br` | `(CompressionLevel level = CompressionLevel.Fastest, int bufferSize = 262144)` | `Fastest` |

```csharp theme={null}
var options = new InsertOptions { Compressor = new ZstdCompressor(level: 1) };
```

<Note>
  *コンプレッサーのインスタンスは共有してください。* 各 `Default` は 1 つの共有インスタンスであり、4 つのコンプレッサーはいずれも複数のスレッドから同時に使用しても安全です。
  `InsertOptions.MaxDegreeOfParallelism` が 1 を超える場合がまさにこれに該当します。1 回の insert では、バッチごとに 1 つのコンプレッサーを使用するためです。
  いずれも `IDisposable` を実装していません。`Default` と同じように、独自のインスタンスを一度だけ生成して再利用してください。
</Note>

<h5 id="custom-compressor">
  カスタムコーデック
</h5>

`IClickHouseCompressor` はpublicであり、実装が提供する必要があるのは次の2つのメンバーのみです:

```csharp theme={null}
public sealed class MyCompressor : IClickHouseCompressor
{
    public string ContentEncoding => "my-codec";

    public Stream Compress(Stream destination, bool leaveOpen) => /* a compressing write stream */;
}
```

サーバーは、指定した `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` としている場合でも同様です。

***

<h3 id="tuning-compression">
  圧縮のチューニング
</h3>

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

<h4 id="the-one-number-that-decides-it">
  判断を左右する唯一の数値
</h4>

圧縮は、コーデックがネットワークより高速である限り価値があります。

このしきい値は、読み取りパスにおいては多くの人が想定するよりも低くなります。ClickHouse は HTTP レスポンスを出力バッファ内でシングルスレッドで圧縮するためです。16 vCPU の ClickHouse Cloud サービス (`hits`、RowBinary、レベル 3) で測定したところ、サーバーはおよそ 100〜200MB/s の速度で圧縮済み出力を生成します。

したがって、結果セットが大きく、かつ同時に処理されるクエリが 1 つだけだと仮定すると、圧縮が割に合わなくなるのはおおむね 100MB/s 付近です。単一の HTTPS ストリームは、同一クラウドリージョン内であればこれを上回ることが一般的ですが、public internet や VPN、リージョン境界をまたぐ場合は通常これを下回ります。

挿入パスでは、より高速な回線でも圧縮が有効なままです。クライアント側は専用のコアで圧縮を行うため、通常はサーバー側のレスポンス圧縮よりも高速だからです。

<h4 id="rough-guide-by-deployment">
  デプロイメント別の大まかな目安
</h4>

| クライアントの実行場所 | 一般的な帯域幅 | 読み取り | 挿入 |
| - | - | - | - |
| 同一ホスト / ループバック | > 500 MB/s | `identity` | `lz4` が最速、または圧縮なし |
| 同一リージョン、同一クラウド | 約100〜500 MB/s | `identity` または `lz4` | `zstd:1` |
| クロスリージョン、同一クラウド | 約10〜100 MB/s | `zstd` | `zstd:3` |
| インターネット / VPN / 異なるクラウド | \< 25 MB/s | `zstd` | `zstd:3` |
| 従量課金または帯域が非常に限られる環境 | \< 5 MB/s | `zstd` | `zstd:5` 以上または `br` |

この表では表現しきれない点が3つあります:

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

読み取りパスは、複数のクエリ間でのみ並列化されます。

<h4 id="choosing-a-codec">
  コーデック の選択
</h4>

| Codec | Ratio | 使いどころ | 注意点 |
| - | - | - | - |
| `lz4` | 最低 | 高速な回線で、bandwidth よりも CPU のほうが希少な場合。デコードコストが群を抜いて安く、小さな結果では最速です。zstd というデフォルトから外れたいときに指定すべき コーデック です。 | **エントロピー符号化を持たない**ため、偏りはあるが繰り返しの少ないデータ (たとえば数値テキストの長い連なり) では、ratio が他の コーデック に大きく劣ります。また `http_zlib_compression_level` を上げたときの不利益が最も大きい コーデック でもあります。レベル 1 → 3 では、バイト数が約 29% 減る代わりに CPU が約 2.7 倍かかります。 |
| `zstd` | 高い | 実ネットワークが介在する場面での汎用的な選択肢。重要となる範囲では CPU あたりの ratio が最良で、レベル 3 では `lz4` をバイト数 *でも* サーバー CPU *でも* 実時間でも上回ります。 | **デコード**は `lz4` より高コストで、計測ではレベル 3 で 1.6 倍でした (ただしレベル 1 では両者は同等です) 。しかも ドライバー は呼び出し元の thread 上でデコードします。特に `http_zlib_compression_level=1` では、`lz4` よりサーバー CPU をわずかに *多く* 消費します。 |
| `gzip` | 中程度 | 相互運用性。プロキシや gateway で普遍的に解釈されます。 | 計測では `lz4` と `zstd` の双方にあらゆる面で劣りました。`zstd` よりサイズが大きいうえ、エンコードには数倍、デコードには 5〜9 倍の CPU を要します。性能ではなく互換性のために選んでください。 |
| `br` | 低いレベルで最高 | bandwidth が本当に制約となっており、その対価として CPU を費やせる場合。 | 高いレベルでは著しく落ち込みます。`http_zlib_compression_level=6` では `zstd` の 3〜4 倍のサーバー CPU を計測しました。デフォルトのリストに含まれるどの fallback token よりも優先されてしまうため、デフォルトでは広告されません。 |

<h4 id="levels">
  Levels
</h4>

レスポンスの圧縮は、単一の server setting である `http_zlib_compression_level` によって制御されます。これは zlib だけでなく、*すべての* HTTP コーデック に適用されます。デフォルトは 3 です。

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

```csharp theme={null}
using var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM events",
    options: new QueryOptions
    {
        AcceptEncoding = "zstd",
        CustomSettings = new Dictionary<string, object> { ["http_zlib_compression_level"] = 1 },
    });
```

<h4 id="measuring-your-own-crossover">
  自分の環境でクロスオーバーポイントを測定する
</h4>

codecと圧縮レベルの選択を最適化する最も手軽な方法は、複数のcodecで同じクエリの実行時間を計測して比較することです。

```csharp theme={null}
foreach (var codec in new[] { "identity", "lz4", "zstd" })
{
    var sw = Stopwatch.StartNew();
    using var reader = await client.ExecuteReaderAsync(
        "SELECT ... FROM big_table",
        options: new QueryOptions { AcceptEncoding = codec });
    while (await reader.ReadAsync()) { }
    Console.WriteLine($"{codec,-9} {sw.ElapsedMilliseconds} ms");
}
```

同じ状況をサーバー側から確認するには、`system.query_log` から `ProfileEvents` を読み出します。該当する行を特定できるように、`QueryOptions.QueryId` を設定してください:

```sql theme={null}
SELECT ProfileEvents['UserTimeMicroseconds'] + ProfileEvents['SystemTimeMicroseconds'] AS cpu_us,
       ProfileEvents['NetworkSendBytes'] AS sent_bytes,
       query_duration_ms
FROM system.query_log
WHERE query_id = 'your-query-id' AND type = 'QueryFinish';
```

自分でベンチマークを取る際の落とし穴が1つあります。`ORDER BY` を伴わない単なる `LIMIT n` は、*実行のたびに異なる行* を返すため、繰り返すたびに異なるデータが圧縮され、比率が単なるノイズになってしまいます。固定された結果セットを対象に比較してください。

***

<h3 id="raw-stream-insert">
  生データストリームによる挿入
</h3>

`InsertRawStreamAsync` を使用すると、CSV、JSON、Parquet などの[サポートされている ClickHouse フォーマット](/ja/reference/formats/index)で、ファイルストリームやメモリストリームから直接データを挿入できます。

**CSV ファイルから挿入する:**

```csharp theme={null}
using var response = await client.InsertRawStreamAsync(
    table: "my_table",
    stream: File.OpenRead("data.csv"),
    format: "CSV",
    columns: ["id", "product", "price"] // Optional: specify columns
);
```

<Warning>
  *ドライバーはストリームの所有権を取得します。* `InsertRawStreamAsync` と `PostStreamAsync` は、リクエストが成功したか失敗したかにかかわらず、完了時点で渡されたストリームを破棄します。自分で破棄したり、その後に再利用したりしないでください。上の例で `FileStream` を `using` で囲んでいないのはこのためです。

  自分で書いた `using` は、ドライバーがストリームを破棄した後に実行されます。`FileStream` や `MemoryStream` であればこの2回目の呼び出しは無害ですが、`Dispose` でプールされたバッファーを返却したり参照カウントを減らしたりするストリームの場合、リソースを二重に解放することになります。

  所有権が移るのは引数が受理された時点です。テーブル、ストリーム、フォーマットの指定漏れによって呼び出しが `ArgumentException` や `ArgumentNullException` をスローした場合、ストリームは依然として呼び出し側のものです。
</Warning>

<Note>
  データインジェストの動作を制御するオプションについては、[フォーマット設定のドキュメント](/ja/reference/settings/formats)を参照してください。
</Note>

***

<h3 id="more-examples">
  その他の例
</h3>

さらに実践的な使用例については、GitHubリポジトリ内の[examplesディレクトリ](https://github.com/ClickHouse/clickhouse-cs/tree/main/examples)を参照してください。

<h2 id="ado-net">
  ADO.NET
</h2>

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

<h3 id="ado-net-datasource">
  ClickHouseDataSource によるライフタイム管理
</h3>

**適切なライフタイム管理と接続プーリングを確実に行うため、接続は必ず `ClickHouseDataSource` から作成してください。** DataSource は内部で単一の `ClickHouseClient` を管理しており、すべての接続はその HTTP 接続プールを共有します。

```csharp theme={null}
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();
```

依存関係の挿入では:

```csharp theme={null}
// 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();
        // 接続を使用...
    }
}
```

<Warning>
  **本番環境のコードで `ClickHouseConnection` を直接作成しないでください**。直接インスタンス化するたびに、新しい HTTP クライアントと接続プールが作成されるため、高負荷時にソケットが枯渇するおそれがあります。

  ```csharp theme={null}
  // これは避けてください - 毎回新しい接続プールが作成されます
  using var conn = new ClickHouseConnection("Host=localhost");
  await conn.OpenAsync();
  ```

  代わりに、必ず `ClickHouseDataSource` を使用するか、単一の `ClickHouseClient` インスタンスを共有してください。
</Warning>

***

<h3 id="ado-net-command">
  ClickHouseCommand の使用
</h3>

SQL を実行するコマンドを接続から作成します。

```csharp theme={null}
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` を返します

***

<h3 id="ado-net-reader">
  ClickHouseDataReader の使用
</h3>

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

```csharp theme={null}
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");
    }
}
```

<h4 id="ado-net-reader-enum-ordinal">
  enum の序数を読み取る
</h4>

`Enum8` または `Enum16` カラムはラベルとしてマテリアライズされます。`GetFieldType` は `string` を返し、
`GetString`、`GetValue`、`GetFieldValue<string>` はいずれもラベルを返します。保持されている値は文字列であるため、
数値系のアクセサは enum カラムに対して `InvalidCastException` をスローします。

ラベルの背後にある数値を取得するには `TryGetEnumOrdinal` を使用します。

```csharp theme={null}
if (reader.TryGetEnumOrdinal(ordinal, out int value))
    Console.WriteLine(value);   // e.g. 1 for 'Active' in Enum8('Active' = 1)
```

`Enum8`/`Enum16` カラム、および cell が NULL でない `Nullable(Enum...)` カラムの場合は、`true` を返し、`value` を設定します。NULL の cell や enum 以外のカラムの場合は、`value` に `0` を設定したうえで `false` を返します。序数は wire 上の signed な値であるため負の値になることがあり、`Enum16` の序数は 1 バイトに収まらない場合があります。

<h2 id="best-practices">
  ベストプラクティス
</h2>

<h3 id="best-practices-connection-lifetime">
  接続の有効期間とプーリング
</h3>

`ClickHouse.Driver` は内部で `System.Net.Http.HttpClient` を使用しています。`HttpClient` にはエンドポイントごとの接続プールがあります。そのため、次の点に注意してください。

* データベースセッションは、接続プールで管理される HTTP 接続を介して多重化されます。
* HTTP 接続はプールによって自動的に再利用されます。
* `ClickHouseClient` または `ClickHouseConnection` オブジェクトを破棄した後でも、接続が維持される場合があります。

**推奨パターン:**

| シナリオ | 推奨される方法 |
| - | - |
| 一般的な用途 | シングルトンの `ClickHouseClient` を使用する |
| ADO.NET / ORMs | `ClickHouseDataSource` を使用する (同じプールを共有する接続が作成されます) |
| DI 環境 | `IHttpClientFactory` を使用して、`ClickHouseClient` または `ClickHouseDataSource` をシングルトンとして登録する |

<Warning>
  カスタムの `HttpClient` または `HttpClientFactory` を使用する場合は、半閉状態の接続によるエラーを避けるため、`PooledConnectionIdleTimeout` をサーバーの `keep_alive_timeout` より小さい値に設定してください。Cloud デプロイメントの既定の `keep_alive_timeout` は 10 秒です。
</Warning>

<Warning>
  共有の `HttpClient` を使わずに、複数の `ClickHouseClient` や単独の `ClickHouseConnection` インスタンスを作成するのは避けてください。各インスタンスはそれぞれ独自の接続プールを作成します。
</Warning>

***

<h3 id="best-practice-datetime">
  DateTime の扱い
</h3>

1. **可能な限り UTC を使用します。** タイムスタンプは `DateTime('UTC')` カラムとして保存し、コードでは `DateTimeKind.Utc` を使用します。これにより、タイムゾーンの曖昧さを排除できます。

2. **タイムゾーンを明示的に扱うには `DateTimeOffset` を使用します。** `DateTimeOffset` は常に特定の時点を表し、オフセット情報を含みます。

3. **SQL の型ヒントでタイムゾーンを指定します。** `Unspecified` の DateTime 値を持つパラメーターを使用して非 UTC カラムを対象とする場合は、SQL にタイムゾーンを含めます。
   ```csharp theme={null}
   var parameters = new ClickHouseParameterCollection();
   parameters.AddParameter("dt", myDateTime);

   await client.ExecuteNonQueryAsync(
       "INSERT INTO table (dt) VALUES ({dt:DateTime('Europe/Amsterdam')})",
       parameters
   );
   ```

***

<h3 id="async-inserts">
  非同期 INSERT
</h3>

[非同期 INSERT](/ja/concepts/features/operations/insert/asyncinserts) では、バッチ化の責任がクライアントからサーバーに移ります。クライアント側でバッチ化する代わりに、サーバーが受信データをバッファに保持し、設定可能なしきい値に基づいてストレージに書き出します。これは、多数のエージェントが小さなペイロードを送信するオブザーバビリティのワークロードのような、高い同時実行性が求められるシナリオで有効です。

`CustomSettings` または接続文字列で非同期 INSERT を有効にします:

```csharp theme={null}
// 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 は即座に完了します。データが永続化される保証はありません。 | データ損失を許容できる場合のみ |

<Warning>
  `wait_for_async_insert=0` では、エラーはフラッシュ時にのみ表面化するため、元の insert までさかのぼって特定できません。また、クライアント側でバックプレッシャーもかからないため、サーバーの過負荷を招くおそれがあります。
</Warning>

**主な設定:**

| Setting | Description |
| - | - |
| `async_insert_max_data_size` | バッファがこのサイズ (バイト) に達したらフラッシュ |
| `async_insert_busy_timeout_ms` | このタイムアウト (ミリ秒) が経過したらフラッシュ |
| `async_insert_max_query_number` | この数のクエリが蓄積したらフラッシュ |

***

<h3 id="best-practices-sessions">
  セッション
</h3>

セッションは、状態を保持するサーバー側の機能が必要な場合にのみ有効にしてください。例:

* 一時テーブル (`CREATE TEMPORARY TABLE`)
* 複数のステートメントにまたがってクエリコンテキストを維持する
* セッションレベルの設定 (`SET max_threads = 4`)

セッションを有効にすると、同じセッションの同時使用を防ぐため、リクエストは直列化されます。そのため、セッション状態を必要としないワークロードではオーバーヘッドが発生します。

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

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 との互換性のため) :**

```csharp theme={null}
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();
```

***

<h2 id="supported-data-types">
  サポートされているデータ型
</h2>

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

<h3 id="clickhouse-native-type-map-reading">
  型マッピング: ClickHouseから読み取る場合
</h3>

<h4 id="type-map-reading-integer">
  整数型
</h4>

| 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` |

***

<h4 id="type-map-reading-floating-points">
  浮動小数点型
</h4>

| ClickHouse型 | .NET型 |
| - | - |
| Float32 | `float` |
| Float64 | `double` |
| BFloat16 | `float` |

***

<h4 id="type-map-reading-decimal">
  Decimal 型
</h4>

| ClickHouse 型 | .NET 型 |
| - | - |
| Decimal(P, S) | `decimal` / `ClickHouseDecimal` |
| Decimal32(S) | `decimal` / `ClickHouseDecimal` |
| Decimal64(S) | `decimal` / `ClickHouseDecimal` |
| Decimal128(S) | `decimal` / `ClickHouseDecimal` |
| Decimal256(S) | `decimal` / `ClickHouseDecimal` |

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

***

<h4 id="type-map-reading-boolean">
  Boolean 型
</h4>

| ClickHouse 型 | .NET 型 |
| - | - |
| Bool | `bool` |

***

<h4 id="type-map-reading-strings">
  String 型
</h4>

| ClickHouse 型 | .NET 型 |
| - | - |
| String | `string` |
| FixedString(N) | `string` |

<Note>
  デフォルトでは、`String` と `FixedString(N)` の両方のカラムは `string` として返されます。代わりに `byte[]` として読み取るには、接続文字列で `ReadStringsAsByteArrays=true` を設定します。これは、有効な UTF-8 でない可能性があるバイナリデータを保存する場合に便利です。

  この設定は他の型の内部にネストされた文字列にも適用されるため、`Array(String)` は `byte[][]` として、`Map(String, String)` は `Dictionary<byte[], byte[]>` として読み取られます (キーも含みます) 。唯一の例外は `JSON` カラムで、その文字列のリーフは常にテキストとして扱われます。[JSON type](#type-map-reading-json) を参照してください。
</Note>

***

<h4 id="type-map-reading-datetime">
  日付と時刻の型
</h4>

| 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()` を使用してください。

```csharp theme={null}
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` を返します。これにより、タイムゾーンについて何も仮定せず、保存されている時刻の値をそのまま正確に保持できます。

明示的なタイムゾーンを持たないカラムでタイムゾーンを考慮した動作が必要な場合は、次のいずれかを行ってください。

1. カラム定義で明示的なタイムゾーンを使用する: `DateTime('UTC')` または `DateTime('Europe/Amsterdam')`
2. 読み取り後に自分でタイムゾーンを適用する。

***

<h4 id="type-map-reading-json">
  JSON 型
</h4>

| 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 をパースせずにそのまま受け渡したい場合や、デシリアライゼーションを自分で処理したい場合に便利です。

```csharp theme={null}
// Configure string mode via settings
var settings = new ClickHouseClientSettings("Host=localhost")
{
    JsonReadMode = JsonReadMode.String
};

// Or via connection string
// "Host=localhost;JsonReadMode=String"
```

`None` は 3 つ目のモードです。読み取り時の動作は `Binary` とまったく同じですが、クエリと共に server setting を送信しません。server setting の設定が許可されていない接続で使用してください。

<h5 id="type-map-reading-json-nulls">
  型付きパスと null
</h5>

カラム型で宣言されたパスは **型付きパス** であり、ドキュメント内のそれ以外のパスは
**動的パス** です。この 2 つは、値が null の場合に挙動が異なります。

型付きパスは常に `JsonObject` に現れます。`Nullable(T)` または `Dynamic` として宣言した場合、格納された値が null のときも、ドキュメントにそのパスが存在しないときも、どちらも JSON の null として返されるため、両者を区別することはできません:

```csharp theme={null}
// Column type JSON(x Nullable(Int64))
// stored '{"x":null}'  ->  {"x":null}
// stored '{}'          ->  {"x":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}}` が得られます。

<Note>
  これは server 自体がレンダリングする内容であるため、`Binary` モードと `String` モードの結果は現在一致しています。1.4.0 より前では、null を保持する型付きパスは `JsonObject` から削除されていたため、`{"x":null}` は `{}` として読み戻されていました — さらに `JSON(a.b Nullable(Int64))` のようなネストされたパスでは、`a` のサブツリー全体が消えていました。
</Note>

<h5 id="type-map-reading-json-strings">
  JSON カラム内の文字列
</h5>

`JSON` カラム内の String リーフは、`ReadStringsAsByteArrays` の設定値にかかわらず、常にテキストとして返されます。`JsonValue` にはバイト配列形式が存在せず、`byte[]` は base64 として出力されてしまうためです。これは `String`、`FixedString`、およびそれらを `LowCardinality`、`Nullable`、`SimpleAggregateFunction` でラップしたもの、さらに `Array` や `Map` 内の文字列 (map のキーを含む) にも当てはまります。

<Note>
  JSON リーダーが型を判別できないバイト配列は、やはり base64 として出力されます。`Variant` や `Dynamic` の型付きパスは、型が行ごとにしか判明しない値を保持するため、`Variant(Array(UInt8), String)` 配下の文字列は base64 エンコードされて返されます。これはどちらの設定でも同じです。

  JSON の map キー型が厳密に `String` 以外の場合 (例えば `Map(LowCardinality(String), String)`) は、`NotSupportedException` がスローされます。
</Note>

<h5 id="overlapping-paths">
  重複するパス
</h5>

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 を保持している場合は、そのいずれの下にもサブツリーを配置できないため、依然として例外がスローされます。

```csharp theme={null}
var settings = new ClickHouseClientSettings("Host=localhost")
{
    AllowDuplicateJsonKeys = true
};

// Or via connection string
// "Host=localhost;AllowDuplicateJsonKeys=true"
```

***

<h4 id="type-map-reading-map">
  Map type
</h4>

| ClickHouse Type | .NET Type | Notes |
| - | - | - |
| Map(K, V) | `Dictionary<K, V>` | デフォルト (`MapReadMode=Dictionary`) |
| Map(K, V) | `List<KeyValuePair<K, V>>` | `MapReadMode=KeyValuePairs` の場合 |

ClickHouse の `Map(K, V)` は物理的には `Array(Tuple(K, V))` であり、同じキーを持つエントリを複数保持できます。一方、`Dictionary` ではそれができないため、デフォルトのモードでは重複したキーは最後の値のみが残り、それより前のペアは破棄されます。`MapReadMode` 設定で表現形式を選択できます:

* **`Dictionary` (デフォルト)**: `Dictionary<K, V>` を返します。

* **`KeyValuePairs`**: サーバーがペアを送信した順序で `List<KeyValuePair<K, V>>` を返すため、キーが重複するエントリも含めてすべてのペアが保持されます。

```csharp theme={null}
// Configure key-value-pair mode via settings
var settings = new ClickHouseClientSettings("Host=localhost")
{
    MapReadMode = MapReadMode.KeyValuePairs
};

// Or via connection string
// "Host=localhost;MapReadMode=KeyValuePairs"
```

mode は `Map` カラムのフレームワーク型を選択するものであり、`GetFieldValue<T>`、ドライバーが報告するスキーマの型、POCO のプロパティマッピングにも適用されます。カラムの型ツリー内に map が現れる箇所すべてに適用され、`Array(Map(...))`、`Map(K, Map(...))`、`Tuple(..., Map(...))`、`Dynamic` も対象に含まれます。

書き込みパスでは、どちらの mode でも両方の表現が受け入れられます。[maps の書き込み](#type-map-writing-other) を参照してください。

***

<h4 id="type-map-reading-other">
  その他の型
</h4>

| 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>`、または `MapReadMode=KeyValuePairs` の場合は `List<KeyValuePair<K, V>>` — [Map type](#type-map-reading-map) を参照 |
| Nullable(T) | `T?` |
| Enum8 | `string` |
| Enum16 | `string` |
| LowCardinality(T) | T と同じ |
| SimpleAggregateFunction | 基底となる型と同じ |
| Nested(...) | `Tuple[]` |
| Variant(T1, T2, ...) | 注を参照 |
| QBit(T, dimension) | `T[]` |

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

***

<h4 id="type-map-reading-geometry">
  ジオメトリ型
</h4>

| ClickHouse 型 | .NET 型 |
| - | - |
| Point | `Tuple<double, double>` |
| Ring | `Tuple<double, double>[]` |
| LineString | `Tuple<double, double>[]` |
| Polygon | `Ring[]` |
| MultiLineString | `LineString[]` |
| MultiPolygon | `Polygon[]` |
| Geometry | 注を参照 |

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

***

<h3 id="clickhouse-native-type-map-writing">
  型マッピング: ClickHouse への書き込み
</h3>

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

<h4 id="type-map-writing-integer">
  整数型
</h4>

| 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()` と互換性のある任意の型 | |

***

<h4 id="type-map-writing-floating-point">
  浮動小数点型
</h4>

| ClickHouse 型 | 受け入れ可能な .NET 型 | 備考 |
| - | - | - |
| Float32 | `float`、および `Convert.ToSingle()` と互換性のある任意の型 | |
| Float64 | `double`、および `Convert.ToDouble()` と互換性のある任意の型 | |
| BFloat16 | `float`、および `Convert.ToSingle()` と互換性のある任意の型 | 16 ビットの bfloat 形式に切り詰められます |

***

<h4 id="type-map-writing-boolean">
  ブール型
</h4>

| ClickHouse 型 | 受け入れ可能な .NET 型 | 備考 |
| - | - | - |
| Bool | `bool` | |

***

<h4 id="type-map-writing-strings">
  文字列型
</h4>

| ClickHouse 型 | 受け入れ可能な .NET 型 | 注記 |
| - | - | - |
| String | `string`, `byte[]`, `ReadOnlyMemory<byte>`, `Stream` | バイナリ型はそのまま書き込まれます。ストリームはシーク可能なものと不可能なものがあります |
| FixedString(N) | `string`, `byte[]`, `ReadOnlyMemory<byte>`, `Stream` | String は UTF-8 でエンコードされたうえでパディングされます。バイナリ型は厳密に N バイトである必要があります |

***

<h4 id="type-map-writing-datetime">
  日付と時刻の型
</h4>

| 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`, `TimeOnly`, `int` | ±999:59:59 に丸められます。`int` は秒として扱われます |
| Time64 | `TimeSpan`, `TimeOnly`, `decimal`, `double`, `float`, `int`, `long`, `string` | `string` は `[-]HHH:MM:SS[.fraction]` として解析され、±999:59:59.999999999 に丸められます |

<Note>
  **範囲外の値**

  バイナリ書き込みパスでは、サポート範囲外の `Date`、`Date32`、`DateTime`、`DateTime32` の値は、`Write` 時に `ArgumentOutOfRangeException` をスローし、カラム型とサポート範囲がエラーメッセージに含まれます。以前は、範囲外の値が 32 ビット整数を介して暗黙的に切り詰められ、サーバーによって再解釈されることで、実在はするものの誤った timestamp が生成される可能性がありました。
</Note>

ドライバーは値の書き込み時に `DateTime.Kind` を考慮します。

| DateTime.Kind | HTTP パラメータ | バルクコピー |
| - | - | - |
| Utc | 時点は保持されます | 時点は保持されます |
| Local | 時点は保持されます | 時点は保持されます |
| Unspecified | パラメータ型のタイムゾーンの壁時計時刻として扱われます (既定では UTC) | カラムのタイムゾーンの壁時計時刻として扱われます |

`DateTimeOffset` の値では、常に正確な時点が保持されます。

**例: UTC DateTime (時点は保持される)**

```csharp theme={null}
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 (時計上の時刻)**

```csharp theme={null}
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` を使用してください。これにより、サーバーのタイムゾーン、クライアントのタイムゾーン、またはカラムのタイムゾーンに関係なく、コードが常に一貫して動作します。

<h4 id="datetime-http-param-vs-bulkcopy">
  HTTP パラメータと Bulk Copy の違い
</h4>

`Unspecified` の DateTime 値を書き込む際、HTTP パラメータのバインドと Bulk Copy には重要な違いがあります。

**Bulk Copy** は対象カラムのタイムゾーンを認識しており、そのタイムゾーンで `Unspecified` の値を正しく解釈します。

**HTTP Parameters** はカラムのタイムゾーンを自動的には認識しません。SQL の型ヒントでそのタイムゾーンを指定する必要があります。

```csharp theme={null}
// 正しい例: 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 として扱われる** | アムステルダム時間として扱われる |

***

<h4 id="type-map-writing-decimal">
  Decimal 型
</h4>

| 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 |

***

<h4 id="type-map-writing-json">
  JSON 型
</h4>

| 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` がスローされます。

```csharp theme={null}
// デフォルトの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>();
```

<h5 id="json-typed-columns">
  型付き JSON カラム
</h5>

JSON カラムに型ヒント (例: `JSON(id UInt64, price Decimal128(2))`) がある場合、ドライバーはそれらのヒントを使って、値を型情報を完全に保ったままシリアライズします。これにより、`UInt64`、`Decimal`、`UUID`、`DateTime64` など、汎用的な JSON としてシリアライズすると精度が失われる可能性のある型でも、精度を保持できます。

<h5 id="json-poco-serialization">
  POCO のシリアライゼーション
</h5>

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 でのみ機能します。**

```sql theme={null}
CREATE TABLE events (
    id UInt32,
    data JSON(`user.id` Int64, `user.name` String, Timestamp DateTime64(3))
) ENGINE = MergeTree() ORDER BY id
```

```csharp theme={null}
using 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 のみです) 。

***

<h4 id="type-map-writing-other">
  その他の型
</h4>

| 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` は空配列として書き込まれます。Nested 型 (`Array(Array(T))` 以上) では、ジャグ配列 (`T[][]`, `List<List<T>>`) と長方形の多次元 CLR 配列 (`T[,]`, `T[,,]`, …) のどちらも受け入れられます。CLR のランクは ClickHouse のネストの深さと一致する必要があります。 |
| Tuple(T1, T2, ...) | `ITuple`, `IList` | 要素数はタプルの要素数と一致する必要があります。要素数が 7 を超える場合は、[ValueTuple の注意点](#valuetuple-caveat) を参照してください。 |
| Map(K, V) | `IDictionary`, `IEnumerable<KeyValuePair<K, V>>` | ペアのシーケンス (たとえば `MapReadMode=KeyValuePairs` が生成する `List<KeyValuePair<K, V>>`) はどちらの読み取りモードでも受け入れられ、キーの重複も可能です。バイナリ挿入とクエリパラメータの両方に適用されます |
| 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 に委譲します。次元はメタデータとしてのみ使用されます |

***

<h4 id="type-map-writing-geometry">
  ジオメトリ型
</h4>

| 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 |

***

<h4 id="type-map-writing-not-supported">
  書き込みではサポートされていません
</h4>

| ClickHouse 型 | 備考 |
| - | - |
| Dynamic | `NotImplementedException` がスローされます |
| AggregateFunction | `AggregateFunctionException` がスローされます |

***

<h3 id="nested-type-handling">
  ネスト型の扱い
</h3>

ClickHouse のネスト型 (`Nested(...)`) は、配列として読み書きできます。

```sql theme={null}
CREATE TABLE test.nested (
    id UInt32,
    params Nested (param_id UInt8, param_val String)
) ENGINE = Memory
```

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

await client.InsertBinaryAsync(
    "test.nested",
    new[] { "id", "params.param_id", "params.param_val" },
    new[] { row1, row2 }
);
```

<h2 id="logging-and-diagnostics">
  ログと診断
</h2>

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

<h3 id="logging-quick-start">
  クイックスタート
</h3>

```csharp theme={null}
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);
```

<h4 id="logging-appsettings-config">
  appsettings.json を使用する
</h4>

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

```csharp theme={null}
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);
```

<h4 id="logging-inmemory-config">
  インメモリ構成を使用する
</h4>

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

```csharp theme={null}
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);
```

<h3 id="logging-categories">
  カテゴリと出力元
</h3>

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

| カテゴリ | ソース | 主な内容 |
| - | - | - |
| `ClickHouse.Driver.Connection` | `ClickHouseConnection` | 接続のライフサイクル、HTTP クライアント ファクトリーの選択、接続のオープン/クローズ、セッション管理。 |
| `ClickHouse.Driver.Command` | `ClickHouseCommand` | クエリ実行の開始/完了、所要時間、クエリ ID、サーバー統計情報、エラーの詳細。 |
| `ClickHouse.Driver.Transport` | `ClickHouseConnection` | 低レベルの HTTP ストリーミング リクエスト、圧縮フラグ、レスポンスのステータスコード、トランスポート エラー。 |
| `ClickHouse.Driver.Client` | `ClickHouseClient` | バイナリ insert、クエリ、その他の操作 |
| `ClickHouse.Driver.NetTrace` | `TraceHelper` | ネットワークトレース (デバッグモードが有効な場合のみ) |

<h4 id="logging-config-example">
  例: 接続の問題を診断する
</h4>

```json theme={null}
{
    "Logging": {
        "LogLevel": {
            "ClickHouse.Driver.Connection": "Trace",
            "ClickHouse.Driver.Transport": "Trace"
        }
    }
}
```

以下の内容がログに記録されます。

* HTTP クライアント ファクトリの選択 (既定のプールまたは単一接続)
* HTTP ハンドラーの設定 (SocketsHttpHandler または HttpClientHandler)
* 接続プールの設定 (MaxConnectionsPerServer、PooledConnectionLifetime など)
* タイムアウトの設定 (ConnectTimeout、Expect100ContinueTimeout など)
* SSL/TLS の設定
* 接続のオープン/クローズ イベント
* セッション ID の追跡

<h3 id="logging-debugmode">
  デバッグモード: ネットワークトレースと診断
</h3>

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

```csharp theme={null}
var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace); // ネットワークイベントを表示するにはTraceレベルが必要
});

var settings = new ClickHouseClientSettings()
{
    LoggerFactory = loggerFactory,
    EnableDebugMode = true,  // 低レベルのネットワークトレースを有効にする
};
```

<h2 id="opentelemetry">
  OpenTelemetry
</h2>

このドライバーは、.NET [`System.Diagnostics.Activity`](https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing) API を通じて、OpenTelemetry の分散トレーシングをネイティブにサポートしています。有効にすると、ドライバーはデータベース操作に対するスパンを出力し、それらを Jaeger や ClickHouse 自体 ([OpenTelemetry Collector](/ja/guides/use-cases/observability/build-your-own/integrating-opentelemetry) 経由) などのオブザーバビリティバックエンドにエクスポートできます。

<h3 id="opentelemetry-enabling">
  トレーシングを有効にする
</h3>

ASP.NET Core アプリケーションでは、ClickHouse ドライバーの `ActivitySource` を OpenTelemetry の設定に追加します。

```csharp theme={null}
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)  // ClickHouse ドライバーのスパンを購読する
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());             // または AddJaegerExporter() など
```

コンソールアプリケーション、テスト、または手動セットアップの場合:

```csharp theme={null}
using OpenTelemetry;
using OpenTelemetry.Trace;

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

<h3 id="opentelemetry-attributes">
  スパン属性
</h3>

各スパンには、標準的な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` | サーバー側の実行時間 (ナノ秒) |

<h3 id="opentelemetry-configuration">
  設定オプション
</h3>

`ClickHouseDiagnosticsOptions` を使用して、トレーシングの動作を制御します。

```csharp theme={null}
using ClickHouse.Driver.Diagnostic;

// スパンにSQLステートメントを含める（デフォルト: セキュリティのためfalse）
ClickHouseDiagnosticsOptions.IncludeSqlInActivityTags = true;

// 長いSQLステートメントを切り詰める（デフォルト: 1000文字）
ClickHouseDiagnosticsOptions.StatementMaxLength = 500;
```

<Warning>
  `IncludeSqlInActivityTags` を有効にすると、トレースに機密データが含まれる可能性があります。本番環境での使用には注意してください。
</Warning>

<h2 id="tls-configuration">
  TLS 設定
</h2>

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

<h3 id="custom-certificate-validation">
  カスタム証明書の検証
</h3>

本番環境でカスタムの証明書検証ロジックが必要な場合は、`ServerCertificateCustomValidationCallback` ハンドラーを構成した独自の `HttpClient` を指定します。

```csharp theme={null}
using System.Net;
using System.Net.Security;
using ClickHouse.Driver;

var handler = new HttpClientHandler
{
    // No AutomaticDecompression needed: the driver decodes compressed responses itself.
    ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
    {
        // Example: Accept a specific certificate thumbprint
        if (cert?.Thumbprint == "YOUR_EXPECTED_THUMBPRINT")
            return true;

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

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

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

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

using var client = new ClickHouseClient(settings);
```

<Note>
  カスタム HttpClient を指定する際の注意事項

  * **自動圧縮解除**: `AutomaticDecompression` は無効のままにしてください。圧縮されたレスポンスはドライバー自身がデコードするため不要であり、有効にするとリクエスト側でかえって不都合が生じます。送信時にハンドラーがマスクに含まれるすべてのアルゴリズムを送信する `Accept-Encoding` に*追加してしまう*ため、ドライバーが提示した範囲が広がり、ClickHouse が要求していない codec で応答する可能性があります。[レスポンスの圧縮解除](#response-decompression)を参照してください。
  * **アイドルタイムアウト**: ハーフオープン接続による接続エラーを避けるため、`PooledConnectionIdleTimeout` はサーバーの `keep_alive_timeout` (ClickHouse Cloud では 10 秒) より短く設定してください。
</Note>

<h2 id="performance-tuning">
  パフォーマンスチューニング
</h2>

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

<h3 id="perf-at-a-glance">
  一目でわかる要点
</h3>

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

***

<h3 id="perf-read-path">
  読み取り: マテリアライズ経路の選択
</h3>

結果から行を取得する方法は3つあり、コストはそれぞれ異なります。一部の経路では値がボックス化されるため、割り当てが増えパフォーマンスが低下します。

| 読み取り方法 | 各値をボックス化 | 備考 |
| - | - | - |
| `QueryAsync<T>` | **いいえ** | ストリームから直接プロパティへ読み取ります。高速な経路です。 |
| 型付きリーダーのアクセサ (`GetInt32`、`GetInt64`、`GetDouble`、`GetGuid`、`GetDateTime`、`GetFieldValue<T>`) | **いいえ** | 型付き値ストアからボックス化せずに読み取ります。 |
| `MapTo<T>` | はい | まず行をマテリアライズし、そこから値をコピーします。 |
| `GetValue` および `GetValues` | はい | `object` を返すため、値を取得する時点でボックス化が必要になります。 |

*hits* データセットの105カラムを1,000,000行読み取った場合:

| API | 割り当て量 |
| - | -: |
| `QueryAsync<T>` | **1,372 MB** |
| `MapTo<T>` | 3,133 MB |

```csharp theme={null}
// Fast path: register the type once, then stream rows directly into it.
client.RegisterPocoType<HitRow>();

await foreach (var row in client.QueryAsync<HitRow>("SELECT * FROM hits"))
    Process(row);
```

<Note>
  *ORM は型付きアクセサを使用する場合に高速パスを利用できます。* linq2db は各カラムに対して `GetInt64`、
  `GetDouble`、`GetDateTime` を登録するため、ボックス化なしで読み取ります。
  `GetValue` 経由で読み取るコード (Dapper の `dynamic` 結果を含む) は、値ごとにボックス化が発生します。ORM のクエリがホットパスにあり、
  `GetValue` 経由で読み取っている場合は、そのクエリに限り `QueryAsync<T>` を使用してください。
</Note>

***

<h3 id="perf-insert-batching">
  挿入: バッチサイズと並列度
</h3>

バッチサイズは、挿入スループットを左右する最も影響の大きい制御項目です。`InsertOptions.BatchSize` のデフォルトは 100,000 行です。

**大きなバッチを使用してください。** 1,000,000 行の挿入で、バッチあたりの行数を 10,000 から 100,000 に増やした結果は次のとおりです。

| 挿入 | 10,000 rows/batch | 100,000 rows/batch | |
| - | -: | -: | -: |
| POCO | 15,308 ms | 7,853 ms | −49% |
| `object[]` | 17,027 ms | 10,671 ms | −37% |

バッチサイズを制御できない場合 (多数の小規模なプロデューサーがそれぞれ独立して行を送信する場合など) は、[非同期 挿入](#async-inserts) を使用し、バッチ化はサーバーに任せてください。

**並列アップロード。** `InsertOptions.MaxDegreeOfParallelism` のデフォルトは `1` です。この値を増やすと、複数のバッチを同時に送信できます。各バッチがそれぞれ別のスレッドで圧縮されるため、圧縮を有効にしている場合に特に効果的です。セッションは並列挿入では機能しません。セッションを無効にするか、`MaxDegreeOfParallelism = 1` のままにしてください。

**スキーマプローブをなくす。** `InsertBinaryAsync` の呼び出しでは、カラム型を判別するために毎回まず `SELECT ... WHERE 1=0` クエリが送信されます。`ColumnTypes` または `UseSchemaCache` によってこのラウンドトリップをなくす方法は、[スキーマプローブクエリのスキップ](#skip-schema-query) を参照してください。

<Note>
  ボックス化を伴わない挿入パスは、デフォルトの `RowBinary` フォーマットに適用されます。`RowBinaryWithDefaults` では `DBDefault` マーカーを見つけるために各値を検査する必要があるため、低速なパスのままとなります。
</Note>

***

<h3 id="perf-compression">
  圧縮: 2つの方向で結論が異なる
</h3>

圧縮とは、CPUを消費して転送バイト数を減らすトレードオフです。このトレードオフが有利になるかどうかは、転送の方向、ClickHouse serverへのconnectionのbandwidth、選択した圧縮 algorithmとデータの相性、そして転送バイトごとに課金が発生するかどうかによって決まります。

**Reads:** serverが同一マシン上で動作している場合を除き、圧縮は有効のままにしてください。これがデフォルトです。圧縮なしの場合と比較すると、レベル1の`zstd`では次の結果が得られました。

| クライアントからserverへ | 圧縮の効果 |
| - | - |
| 同一ホスト(ループバック) | 8%のコスト増 |
| 同一クラウドリージョン | **16%の削減** |
| 1リージョン離れた場所 | **33%の削減** |

**挿入:** 圧縮を有効にする前に必ず測定してください。有効化に見合うだけの削減効果が得られないこともあります。また、伸長処理はserverに追加の負荷をかける点にも留意してください。ZstdやLZ4であれば負荷は軽微ですが、他のアルゴリズム(例: Brotli)では高くなることがあります。

挿入時の圧縮を無効にするには、次のようにします。

```csharp theme={null}
var options = new InsertOptions { Compressor = null };
await client.InsertBinaryAsync("my_table", columns, rows, options);
```

codecの選択、圧縮レベル、そして自身の環境における交差点の見つけ方については、[圧縮のチューニング](#tuning-compression)を参照してください。

***

<h3 id="perf-buffers">
  Buffers
</h3>

`ReadBufferSize` は、HTTP レスポンスを読み取るバッファのサイズを設定します。デフォルトは 64 KiB です。

ドライバーはこのバッファを共有プールから借用し、リーダー を破棄する際に返却するため、クエリごとに割り当てが発生することはありません。この値を大きくすると、大きな結果セットでのバッファ再充填の回数を減らせます。ドライバーは同時にオープンしている リーダー ごとに 1 つのバッファを保持するため、メモリ使用量はバッファサイズと同時実行される リーダー の数に比例して増加します。

```csharp theme={null}
var settings = new ClickHouseClientSettings("Host=localhost") { ReadBufferSize = 256 * 1024 };
```

<Warning>
  *リーダーは必ず破棄してください。* リーダーを破棄すると、プールされたバッファが返却され、HTTP接続が解放されます。破棄せずに放置すると、バッファはプールに返却されず、HTTP接続が使用不可のまま残る可能性があります。通常のガベージコレクションは破棄の代わりにはなりません。
</Warning>

***

<h3 id="perf-gc">
  ランタイムと GC
</h3>

**挿入 の多いアプリケーションでは Server GC を有効にしてください。** 同一のコード、同一の割り当てバイト数であっても、Workstation GC は Server GC に比べて 挿入 が最大 97% 低速でした。

```xml theme={null}
<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
```

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 回でした。

<Note>
  Server GC は スループット のための設定であり、レイテンシ のための設定ではありません。同じ計測では、Server GC は一時停止の合計時間が半分未満であった一方、個々の一時停止は長くなりました (95 パーセンタイルで 114.6 ms 対 61.9 ms) 。サービスが tail レイテンシ の影響を受けやすい場合は、いずれかを選ぶ前に両方の mode を計測してください。
</Note>

***

<h3 id="perf-latency">
  レイテンシ: 接続を再利用する
</h3>

新しい TCP 接続の確立と TLS ハンドシェイクには、かなりの時間がかかります。
接続を再利用すれば、クエリのレイテンシを大幅に削減できます。

* リクエストごとにクライアントを作成しないでください。独自の `HttpClient` を持つクライアントを新たに作成するたびに、新しい接続プールが作られ、
  ハンドシェイクのコストが再び発生します。アプリケーションの存続期間を通じて 1 つの `ClickHouseClient` を使い回してください。これはスレッドセーフであり、
  シングルトンとしての利用を想定して設計されています。
* ADO.NET や ORM では `ClickHouseDataSource` を使用し、すべての接続が 1 つのプールを共有するようにしてください。

パターンの全体像については、
[接続のライフタイムとプーリング](#best-practices-connection-lifetime)を参照してください。

***

<h3 id="perf-measuring">
  自分で計測する
</h3>

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

サーバー側が担った処理を確認するには、`QueryOptions.QueryId` を設定し、カウンターを読み取ります:

```sql theme={null}
SELECT ProfileEvents['UserTimeMicroseconds'] + ProfileEvents['SystemTimeMicroseconds'] AS cpu_us,
       ProfileEvents['NetworkSendBytes'] AS sent_bytes,
       query_duration_ms
FROM system.query_log
WHERE query_id = 'your-query-id' AND type = 'QueryFinish';
```

***

<h2 id="orm-support">
  ORM サポート
</h2>

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

```csharp theme={null}
// DataSourceをシングルトンとして登録する
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default");

// ORM用のコネクションを作成する
await using var connection = await dataSource.OpenConnectionAsync();
// コネクションをORMに渡す...
```

<h3 id="orm-support-dapper">
  Dapper
</h3>

`ClickHouse.Driver` は Dapper に対応しています。ドライバーは、Dapper の `@parameter` 構文を ClickHouse のネイティブな `{parameter:Type}` 構文に自動変換し、型は .NET の値から推論されます。

適切に connection のライフタイムを管理するには、`ClickHouseDataSource` を使用します。

```csharp theme={null}
var dataSource = new ClickHouseDataSource("Host=localhost");
services.AddSingleton(dataSource); // DIにシングルトンとして登録

using var connection = dataSource.CreateConnection();
```

<h4 id="dapper-parameter-passing">
  パラメーターの受け渡し形式
</h4>

標準的な Dapper のパラメーター指定方法をすべてサポートしています。

**匿名オブジェクト:**

```csharp theme={null}
await connection.ExecuteAsync(
    "INSERT INTO users (id, name, balance) VALUES (@Id, @Name, @Balance)",
    new { Id = 1, Name = "alice", Balance = 3.14 });
```

**POCO クラス：**

```csharp theme={null}
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:**

```csharp theme={null}
var parameters = new Dictionary<string, object> { { "Id", 2 } };
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id = @Id", parameters);
```

**`DynamicParameters` (ディクショナリまたは匿名オブジェクトから) :**

```csharp theme={null}
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);
```

<h4 id="dapper-pocos">
  POCO へのクエリ
</h4>

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

```csharp theme={null}
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();
```

<h4 id="dapper-clickhouse-param-syntax">
  ClickHouseネイティブのパラメーター構文
</h4>

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

```csharp theme={null}
var parameters = new Dictionary<string, object> { { "value", 42 } };
var result = await connection.QueryAsync<int>("SELECT {value:Int32}", parameters);
```

<h4 id="dapper-where-in">
  WHERE IN
</h4>

**DapperのネイティブなIN展開が機能します：**

```csharp theme={null}
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()` も動作します:**

```csharp theme={null}
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);
```

<h4 id="dapper-type-handlers">
  カスタム型ハンドラー
</h4>

`ITuple`、`BigInteger`、`ClickHouseDecimal` など、一部の ClickHouse の型では、起動時にハンドラーを登録する必要があります。

```csharp theme={null}
// 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 の例](https://github.com/ClickHouse/clickhouse-cs/blob/main/examples/ORM/ORM_001_Dapper.cs)を参照してください。

<h4 id="dapper-contrib">
  Dapper.Contrib
</h4>

`GetAll<T>()` と `Get<T>(id)` は動作します。`Insert<T>()` は動作しません。これは SQL Server の構文 (`SCOPE_IDENTITY`、`[]`) を生成するためです。代わりに、ClickHouseClient のネイティブな `InsertBinaryAsync` メソッドを使用することを推奨します。

```csharp theme={null}
[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 のカラム名と完全に一致している必要があります (大文字と小文字を区別します) 。

<h4 id="dapper-limitations">
  制限事項
</h4>

| 項目 | ステータス | 詳細 |
| - | - | - |
| **結果**としての 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 での表現がありません |

<h3 id="orm-support-linq2db">
  Linq2db
</h3>

このドライバーは、.NET 向けの軽量な ORM／LINQ プロバイダーである [linq2db](https://github.com/linq2db/linq2db) に対応しています。詳細なドキュメントについては、プロジェクトの Web サイトを参照してください。

**使用例:**

ClickHouse プロバイダーを使用して `DataConnection` を作成します。

```csharp theme={null}
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 を使用して定義できます。クラス名とプロパティ名がテーブル名およびカラム名と完全に一致している場合は、設定は不要です。

```csharp theme={null}
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}
```

**クエリの実行:**

```csharp theme={null}
await using var db = new DataConnection(options);

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

**バルクコピー:**

効率的な一括挿入には `BulkCopyAsync` を使用します。

```csharp theme={null}
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);
```

<h3 id="orm-support-ef-core">
  Entity Framework Core
</h3>

ClickHouse 向けの公式 Entity Framework Core プロバイダーです。C# クラスを ClickHouse テーブルにマッピングし、LINQ でクエリを実行し、`SaveChanges` を通じてデータを insert できます。いずれも使い慣れた EF Core のパターンで行えます。

* **NuGet**: [`ClickHouse.EntityFrameworkCore`](https://www.nuget.org/packages/ClickHouse.EntityFrameworkCore)
* **Source**: [GitHub](https://github.com/ClickHouse/ClickHouse.EntityFrameworkCore)

<Note>
  このプロバイダーは現在も活発に開発が進められています。現行の release では、LINQ クエリ (JOIN、subqueries、set operations を含む) 、`SaveChanges` / `BulkInsertAsync` による `INSERT`、完全な DDL (CREATE / ALTER / DROP) を伴う移行、そして ClickHouse 固有の table engine 設定をサポートしています。`UPDATE` / `DELETE` には対応していません。
</Note>

<h4 id="ef-core-installation">
  インストール
</h4>

```bash theme={null}
dotnet add package ClickHouse.EntityFrameworkCore
```

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

<h4 id="ef-core-quick-start">
  クイックスタート
</h4>

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

```csharp theme={null}
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();
```

<h4 id="ef-core-types">
  サポートされる型
</h4>

| カテゴリ | 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 桁に制限されます。

<h4 id="ef-core-linq">
  サポートされている LINQ 操作
</h4>

**クエリ:** `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 セマンティクス](#ef-core-join-nulls) を参照) 。

**サブクエリ:** 相関 `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 の関数に変換されます — 算術、対数、三角、およびユーティリティ関数。

<h5 id="ef-core-join-nulls">
  LEFT JOIN の NULL セマンティクス
</h5>

このプロバイダーは、JOIN の挙動に関する Entity Framework の想定に合わせるため、接続のたびに `set_join_use_nulls=1` を自動的に設定します。

ClickHouse サーバーまたは profile でこの設定の変更が禁止されている場合 (例: `readonly=1` の profile) 、次のように無効化してください。

```csharp theme={null}
optionsBuilder.UseClickHouse(connectionString, o => o.DisableJoinNullSemantics());
```

オプトアウトが有効な場合、LEFT JOIN は ClickHouse のカラムのデフォルト値を返すため、EF の null ベースのナビゲーション検出は期待どおりに機能しなくなります。`== null` の代わりに、`0` / `""` との明示的な比較を使用してください。

<h4 id="ef-core-insert">
  データの挿入
</h4>

`SaveChanges` では、ドライバーが提供するネイティブの `InsertBinaryAsync` API を使用します。RowBinary エンコーディングと圧縮されたリクエストボディを利用するため、パラメーター化 SQL よりもはるかに効率的です。

```csharp theme={null}
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) :

```csharp theme={null}
optionsBuilder.UseClickHouse("Host=localhost", o => o.MaxBatchSize(5000));
```

<h4 id="ef-core-bulk-insert">
  一括挿入
</h4>

高スループットの読み込みでは、`SaveChanges` ではなく `BulkInsertAsync` を使用してください。これは `DbContext` の拡張メソッドで、EF Core の変更トラッカー、ID 解決、状態管理を完全にバイパスし、RowBinary エンコーディングと圧縮されたリクエストボディを使用して、ドライバーの `InsertBinaryAsync` を直接呼び出します。

そのため、挿入後にエンティティの追跡が不要な大規模データセットの読み込みに適しています。

```csharp theme={null}
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` の状態遷移は発生しません。

<h4 id="ef-core-enums">
  列挙型
</h4>

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

```csharp theme={null}
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();
```

<h4 id="ef-core-value-converters">
  カスタム型の変換
</h4>

EF Core の `ValueConverter` システムを使うと、カスタム型をプロバイダーがすでにサポートしている型にマッピングできます。プロバイダーがカスタム型を直接扱うことはなく、EF Core がその境界で変換を行います。

**プロパティ単位の変換:**

```csharp theme={null}
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");
```

**再利用可能なコンバータークラス:**

```csharp theme={null}
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>();
}
```

<h4 id="ef-core-column-types">
  カラム型のアノテーション
</h4>

`string`、`int`、`DateTime` などのスカラー型では、プロバイダーが ClickHouse の型を自動的に推論します。パラメーター化された型やラッパーについては、ClickHouse の型を明示的に指定する必要があります。

**データ アノテーション (属性) を使用する場合:**

```csharp theme={null}
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 を使用する方法:**

```csharp theme={null}
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` をどのネストレベルでも自動的にアンラップします。

<h4 id="ef-core-variant-dynamic">
  Variant と Dynamic カラム
</h4>

ClickHouse の `Variant(T1, T2, ...)` カラムおよび `Dynamic` カラムは、.NET では `object` にマップされます。`object` は自動的な型推論には汎用的すぎるため、`.HasColumnType()` でストア型を明示的に指定する必要があります。

```csharp theme={null}
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[]`) に自動的にデシリアライズされます。

<h4 id="ef-core-json">
  JSON カラム
</h4>

このプロバイダーは ClickHouse の `Json` カラム型をサポートしており、`System.Text.Json.Nodes.JsonNode` (プライマリ) または `string` (自動 `ValueConverter` 使用時) に対応付けられます。

```csharp theme={null}
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` の両方で利用できます:

```csharp theme={null}
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` を自動的に適用します。

```csharp theme={null}
public class Event
{
    public long Id { get; set; }
    public string? Data { get; set; }  // 生のJSON文字列
}

entity.Property(e => e.Data).HasColumnType("Json");
```

<Note>
  * **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>()` を使用してください。
</Note>

<h4 id="ef-core-engines">
  テーブルエンジン
</h4>

`ToTable(name, t => ...)` のフルーエント API を使用して、ClickHouse テーブルのエンジンとエンジン固有の句を設定します。エンジンが設定されていない場合、プロバイダーは既定で `MergeTree` を使用し、`ORDER BY` はエンティティの主キーに基づいて決定されます。

```csharp theme={null}
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(...)` で指定します:

```csharp theme={null}
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 では一意性が保証されないため、例外がスローされます。

<h4 id="ef-core-migrations">
  移行
</h4>

標準的な EF Core の移行ワークフロー:

```bash theme={null}
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` および移行を介して実行 |

<h4 id="ef-core-limitations">
  移行の制限事項
</h4>

| 機能 | 理由 |
| - | - |
| 外部キー | ClickHouse は外部キーを強制しません。移行では `AddForeignKey` は拒否され、モデル バリデーターはモデルのビルド時に警告を出します。 |
| 一意制約 / 一意索引 | ClickHouse は一意性を保証しません。一意索引は移行時に例外をスローします。 |
| サーバー生成値 (auto-increment / `IDENTITY`) | ClickHouse に相当する機能はありません。 |
| `Nested(…)` カラム | マップされた CLR 型としてはまだサポートされていません。 |
| JSON としての所有エンティティ (`.ToJson()`) | 所有エンティティに対する構造的な JSON マッピングはまだ実装されていません。代わりに、`Json` カラムで `JsonNode` / `string` を使用してください ([JSON カラム](#ef-core-json) を参照) 。 |

移行以外にも、このプロバイダーはまだ以下をサポートしていません。

* **`UPDATE` / `DELETE`**
* **トランザクション**: `BeginTransaction` は no-op です。ClickHouse は ACID トランザクションをサポートしていません。
* **JSON パス クエリの変換**: LINQ の `entity.Data["key"]` は、ClickHouse の `data.key` SQL 構文に変換されません。JSON 以外のカラムでフィルタリングし、JSON はメモリ上で確認してください。

<h2 id="limitations">
  制限事項
</h2>

<h3 id="valuetuple-caveat">
  要素が 8 個以上で、最後の位置にネストしたタプルがある Tuple
</h3>

要素数が 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 層ラップします。

```csharp theme={null}
// 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")))
```

***

<h3 id="aggregatefunction-columns">
  AggregateFunction カラム
</h3>

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

挿入するには:

```sql theme={null}
INSERT INTO t VALUES (uniqState(1));
```

取得するには:

```sql theme={null}
SELECT uniqMerge(c) FROM t;
```

***
