ClickHouse.Driver
1.3.0-rc1
dotnet add package ClickHouse.Driver --version 1.3.0-rc1
NuGet\Install-Package ClickHouse.Driver -Version 1.3.0-rc1
<PackageReference Include="ClickHouse.Driver" Version="1.3.0-rc1" />
<PackageVersion Include="ClickHouse.Driver" Version="1.3.0-rc1" />
<PackageReference Include="ClickHouse.Driver" />
paket add ClickHouse.Driver --version 1.3.0-rc1
#r "nuget: ClickHouse.Driver, 1.3.0-rc1"
#:package ClickHouse.Driver@1.3.0-rc1
#addin nuget:?package=ClickHouse.Driver&version=1.3.0-rc1&prerelease
#tool nuget:?package=ClickHouse.Driver&version=1.3.0-rc1&prerelease
ClickHouse C# client
About
Official C#/ADO.NET client for ClickHouse.
- High performance
- Supports bulk insertion
- Uses compressed binary protocol over HTTP(S)
- Available for .NET Core/Framework/Standard
Documentation
See the ClickHouse website for full documentation.
Usage examples
We have a wide range of examples, aiming to cover typical scenarios of client usage.
ClickHouse Versions
The supported versions of the ClickHouse database server coincide with the versions currently receiving security updates.
For the list of currently supported versions, see here.
Contact us
If you have any questions or need help, feel free to reach out to us in the Community Slack or via GitHub issues.
Contributing
Contributions are welcome and highly appreciated! Check out our contributing guide.
Acknowledgements
Originally created by Oleg V. Kozlyuk
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Http (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- NodaTime (>= 3.2.3)
-
net6.0
- Microsoft.Extensions.Http (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- NodaTime (>= 3.2.3)
- System.Text.Json (>= 10.0.5)
-
net8.0
- Microsoft.Extensions.Http (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- NodaTime (>= 3.2.3)
- System.Text.Json (>= 10.0.5)
-
net9.0
- Microsoft.Extensions.Http (>= 10.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- NodaTime (>= 3.2.3)
- System.Text.Json (>= 10.0.5)
NuGet packages (20)
Showing the top 5 NuGet packages that depend on ClickHouse.Driver:
| Package | Downloads |
|---|---|
|
EntityFrameworkCore.ClickHouse
ClickHouse provider for Entity Framework Core. |
|
|
FreeSql.Provider.ClickHouse
FreeSql 数据库实现,基于 ClickHouse.Client Ado.net |
|
|
linq2db.LINQPad
Supported databases: IBM DB2 LUW/zOS/iSeries, Firebird, IBM Informix, Microsoft Access, Microsoft Sql Server (+Azure), Microsoft Sql Server Compact, MySql, MariaDB, Oracle, PostgreSQL, SQLite, SAP HANA, SAP/Sybase ASE, ClickHouse. |
|
|
ClickHouse.Facades
Raw SQL migrations and contexts for ClickHouse |
|
|
Serilog.Sinks.ClickHouse
Serilog sink for ClickHouse |
GitHub repositories (4)
Showing the top 4 popular GitHub repositories that depend on ClickHouse.Driver:
| Repository | Stars |
|---|---|
|
dotnetcore/FreeSql
.NET aot orm, VB.NET/C# orm, Mysql/PostgreSQL/SqlServer/Oracle orm, Sqlite/Firebird/Clickhouse/DuckDB orm, 达梦/金仓/虚谷/翰高/高斯 orm, 神通 orm, 南大通用 orm, 国产 orm, TDengine orm, QuestDB orm, MsAccess orm.
|
|
|
linq2db/linq2db
Linq to database provider.
|
|
|
dotnet/nuget-trends
Check out NuGet packages adoption and what's trending on NuGet.
|
|
|
sitkoru/Sitko.Core
Sitko.Core is a set of libraries to help build .NET Core applications fast
|
| Version | Downloads | Last Updated |
|---|---|---|
| 1.3.0-rc1 | 127 | 6/24/2026 |
| 1.2.0 | 134,911 | 4/14/2026 |
| 1.1.0 | 117,449 | 3/24/2026 |
| 1.0.2 | 32,195 | 3/10/2026 |
| 1.0.1 | 30,512 | 3/3/2026 |
| 1.0.0 | 36,065 | 2/17/2026 |
| 1.0.0-rc2 | 282 | 2/12/2026 |
| 1.0.0-rc1 | 6,710 | 1/21/2026 |
| 0.9.0 | 132,583 | 12/3/2025 |
| 0.8.1 | 44,625 | 11/19/2025 |
| 0.8.0 | 3,368 | 11/19/2025 |
| 0.7.20 | 317,453 | 8/19/2025 |
| 0.7.19 | 63,994 | 8/13/2025 |
| 0.7.18 | 10,426 | 8/2/2025 |
v1.3.0
---
**New Features:**
* **POCO reads**: stream query results directly into your own classes.
- `ClickHouseClient.QueryAsync<T>(...)` returns `IAsyncEnumerable<T>`; rows are materialized lazily and the underlying reader is disposed when enumeration completes, faults, or stops early.
- `ClickHouseDataReader.MapTo<T>()` materializes the current row into a registered POCO without advancing the reader.
- **Registration**: use `RegisterPocoType<T>()`, it sets up both the insert and read mappings, validating both up front. `RegisterBinaryInsertType<T>()` is unchanged and remains insert-only for backwards compatibility.
- **Type requirements**: a public parameterless constructor and at least one public property with a public non-init setter. `required` properties are supported.
- **Column matching** is case-sensitive (`StringComparer.Ordinal`); missing result columns leave properties at their default value, extra result columns are ignored.
- **No automatic conversions**: type mismatches throw `InvalidOperationException` with the POCO type, property, column, and returned CLR type. Static mismatches fail fast at first `MapTo<T>()` call (or first iteration of `QueryAsync<T>()`) before any rows are materialized.
- **Registration diagnostics**: when a `LoggerFactory` is configured, `RegisterPocoType<T>()` / `RegisterBinaryInsertType<T>()` emit a `Debug`-level log (category `ClickHouse.Driver.Client`) listing which properties mapped to which columns and which were skipped and why.
* **Nested array parameters and multidimensional arrays** (issue #320): `Array(Array(T))` and deeper nestings are now supported end-to-end via parameterized queries, binary inserts, and bulk inserts. Both jagged CLR shapes (`T[][]`, `List<List<T>>`) and rectangular multidimensional CLR shapes (`T[,]`, `T[,,]`, …) are accepted on the write path. Reads return jagged `T[][]` via `GetValue`; callers who know their data is rectangular can use `reader.GetFieldValue<T[,]>(ordinal)` and the driver materialises the column as that CLR shape (throws `InvalidOperationException` on ragged data).
* **ValueTuple support on write path**: `System.ValueTuple` values (C# tuple literals like `(1, "hello")`) are now supported in binary inserts, HTTP parameterized queries, and automatic type inference. Tuples with more than 7 elements are correctly flattened from the compiler-generated rest-nesting structure. Note: if you need exactly 7 scalar elements followed by a nested tuple as the 8th element, wrap the inner tuple in an extra layer (e.g., `Tuple.Create(1,...,7, Tuple.Create(Tuple.Create("a","b")))`) so the driver can distinguish it from TRest nesting.
* **Configurable parameter value formatting**: new `IParameterFormatter` interface allowing configuration of how parameter values are serialized for HTTP transport (sibling to `IParameterTypeResolver`, which governs type resolution). Set `ParameterFormatter` on `ClickHouseClientSettings` to override the built-in serialization logic for any CLR type (e.g., custom `DateTime` precision, decimal culture, string escaping). Includes one implementation, `DictionaryParameterFormatter`, for simple CLR-type → format-function mappings. Return `null` from the formatter to fall through to the built-in formatter. Can also be set per-query via `QueryOptions.ParameterFormatter`. The formatter is also invoked for every element inside composite values (Array, Tuple, Map, Nested); see docs for quoting caveats when formatting string-like types inside composites.
* **Per-query `Accept-Encoding` override**: new `QueryOptions.AcceptEncoding` (mirrored on `ClickHouseCommand.AcceptEncoding`) replaces the default `gzip, deflate` Accept-Encoding header for a single request. Supports multiple algorithms with quality weights (e.g. `"zstd, gzip;q=0.5"`) and forces `enable_http_compression=1` on the URL so ClickHouse honours the header. For codecs the BCL cannot decode (zstd, lz4) the underlying `HttpClient` must be configured with `AutomaticDecompression = None` and the body consumed via `ExecuteRawResultAsync`.
* **`ClickHouseRawResult.ContentEncoding`**: exposes the response body's `Content-Encoding` for callers using `ExecuteRawResultAsync` to decode it themselves; `identity` is normalized to `null`.
* **Customizable read value conversion**: new `IReadValueConverter` interface allows transformation of values returned by the data reader after deserialization (e.g., `DateTime.SpecifyKind` to set UTC kind, string trimming/normalization). Set `ReadValueConverter` on `ClickHouseClientSettings` to apply transformations globally, or override per-query via `QueryOptions.ReadValueConverter`. The converter intercepts `GetValue()` and `GetFieldValue<T>()` calls, and is also applied during POCO materialization (`MapTo<T>()` / `QueryAsync<T>()`). When no converter is set, there is zero performance overhead. Includes one implementation, `DictionaryReadValueConverter`, with a fluent `.For<T>(value => …)` registration for dispatch based on CLR type.
* Added a `ClickHouseDataReader.GetFieldValue<T>(string name)` overload that resolves the column by name, complementing the existing ordinal-based overload.
* **Application-identity tagging in User-Agent**: new `ClickHouseClientSettings.ApplicationInfo` property: an `IReadOnlyDictionary<string, string>` of free-form tags (e.g. `app`, `ver`, `env`) appended to the HTTP `User-Agent` header as a comment token (e.g. `(app:MyApp; ver:2.3.1; env:prod)`).
* **Server-side `Identifier` query-parameter type**: `{name:Identifier}` parameters (and explicit `ClickHouseDbParameter.ClickHouseType = "Identifier"`) are now supported, letting you safely bind a database/table/column name instead of a quoted string literal — e.g. `CREATE DATABASE {name:Identifier}` or `SELECT {col:Identifier} FROM t`. The value is sent verbatim and the server substitutes it as a bare SQL identifier, applying its own backtick quoting/escaping, so identifiers containing special characters (including backticks) round-trip safely with no client-side escaping. Previously these threw `ArgumentException: Unknown type: Identifier` (surfaced from clickhouse-go#1635).
* **Configurable response read buffer size**: new `ClickHouseClientSettings.ReadBufferSize` (and connection-string key `ReadBufferSize`) controls the size of the buffer used when reading HTTP query responses. **Behavioral change:** the default has been lowered from 512 KiB to 8 KiB. The previous 512KiB size exceeded the large object heap limit and could cause substantial performance issues due to GC. Workloads with large responses that prefer fewer buffer refills can set a larger value.
**Improvements:**
* HTTP parameter mismatch errors now include the parameter name, the full ClickHouse type, and the value's runtime CLR type. The previous message (`"Cannot convert 219 to Array(UInt8)"`) collapsed the outer type, omitted which parameter failed, and didn't say what the value actually was.
* `GetFieldValue<T[,]>` errors are now categorised by cause. `InvalidCastException` covers type-structure mismatches: the column is null/DBNull, the value isn't a collection, the source's structural depth differs from the target rank (shallower or deeper), or a leaf is the wrong scalar type or `null` into a non-nullable value-type target. Messages include the column ordinal, target CLR type, and offending indices where applicable. `InvalidOperationException` is reserved for shape-validation failures — the value's structure matches `T` but rows are ragged or an intermediate row is null. Previously, structural-depth mismatches and a `null` leaf into e.g. `int[,]` either threw `InvalidOperationException` or were silently coerced to `default(int)`.
**Bug Fixes:**
* **Breaking Change**: Fixed timezone shift for `@`-style `DateTime`/`DateTimeOffset` parameters on non-UTC servers (issue #350). When a parameter's ClickHouse type was inferred (no explicit `{name:Type}` hint in SQL and no `parameter.ClickHouseType` set), the driver previously emitted a bare `DateTime` hint and the server parsed the wire wall-clock in `session_timezone`/server tz — silently shifting instant-bearing values by the server's offset. Inferred types for `DateTime { Kind: Utc or Local }` and `DateTimeOffset` are now sent as `DateTime('UTC')`, preserving the instant across any server timezone. Explicit hints (`{name:DateTime}` in SQL or `parameter.ClickHouseType`) are untouched, users who specify the type continue to own timezone correctness.
* Fixed silent 32-bit wraparound on the binary write path for `DateTime`/`DateTime32` values outside ClickHouse's supported range (1970-01-01 .. 2106-02-07 06:28:15 UTC). Out-of-range values, e.g. `new DateTime(1900, 1, 1)`, were being cast through a 32-bit signed int and reinterpreted as `UInt32` by the server, producing real-but-wrong timestamps (e.g. `2036-02-07`). The same audit fixed analogous silent out-of-range writes for `Date32` (server would clamp to `[1900-01-01, 2299-12-31]`) and tightened the existing `Date` `OverflowException` into a descriptive `ArgumentOutOfRangeException`. All four types now throw `ArgumentOutOfRangeException` at `Write` time naming the column type and supported range.
* Fixed silent wall-clock shift for `DateTime` and `DateTime64` columns declared with ClickHouse's synthetic `Fixed/UTC±HH:MM:SS` timezone names (e.g. `DateTime('Fixed/UTC+05:30:00')`). These names are not in the IANA TZDB, so `GetZoneOrNull` returned `null`, causing the driver to interpret the stored instant as UTC and return a value shifted by the column's offset. The driver now recognises the `Fixed/UTC` pattern and constructs a correct fixed-offset `DateTimeZone` from it (issue #370).
* Fixed HTTP parameter serialization for `Date`, `DateTime`, and `DateTime64` values inside composite types such as `Array`, `Tuple`, `Map`, and `Variant`. These values are now quoted correctly when sent over HTTP.
* Fixed type inference for `System.Tuple` with more than 7 elements. The TRest nesting was not being flattened, causing the 8th+ elements to be inferred as nested tuple types instead of their actual flat types. This could lead to incorrect ClickHouse type inference and serialization errors.
* Fixed parsing of enum labels containing escaped quotes, parentheses, and `=` characters. This fixes cases like `variantType()` on `Variant(String, DateTime('UTC'))`, which could previously round-trip through the driver as an empty string.
* Fixed `ClickHouseServerException.Message` returning compressed binary bytes when the server returned a non-2xx response with a `Content-Encoding` header. The driver now decompresses gzip / deflate / brotli error bodies before attaching them to the exception. Unknown codecs (zstd, lz4) yield a placeholder pointing at `system.query_log`.
v1.2.0
---
**New Features:**
* **POCO binary inserts**: new `InsertBinaryAsync<T>` overload on `ClickHouseClient` accepts `IEnumerable<T>` directly, mapping public properties to columns automatically. Register types upfront with `RegisterBinaryInsertType<T>()`. Customize column names and ClickHouse types with `[ClickHouseColumn(Name = "...", Type = "...")]`, or exclude properties with `[ClickHouseNotMapped]`. When all properties specify explicit types via the attribute, the schema probe is skipped entirely.
* **Configurable parameter type resolution**: new `IParameterTypeResolver` interface allowing configuration of type mapping for `@`-style parameterized queries. Set `ParameterTypeResolver` on `ClickHouseClientSettings` to override how .NET types are mapped to ClickHouse types (e.g., `DateTime` → `DateTime64(3)`, `decimal` → `Decimal64(4)`). Includes one implementation, `DictionaryParameterTypeResolver` for simple type→type mappings, and supports custom implementations for value-aware or name-based resolution. Can also be set per-query via `QueryOptions.ParameterTypeResolver`.
**Improvements:**
* Type inference now inspects `IPAddress.AddressFamily` to correctly distinguish between IPv4 and IPv6 types. Previously, all `IPAddress` values were inferred as IPv4. This also works for collections, tuples, and maps containing `IPAddress` values.
**Internal Improvements:**
* Centralized parameter type resolution into `ParameterTypeResolution`, replacing previously scattered logic in `ClickHouseDbParameter.QueryForm` and `HttpParameterFormatter`. Each parameter's type is now resolved exactly once per request, ensuring consistency between SQL placeholder generation and HTTP value formatting.
**Bug Fixes:**
* `JsonReadMode` and `JsonWriteMode` will now correcly set the corresponding settings when set to `Binary` mode.
v1.1.0
---
**New Features:**
* `InsertOptions.ColumnTypes`: provide a dictionary of column name → ClickHouse type string to skip the schema probe query (`SELECT ... WHERE 1=0`) entirely. Ideal when the table schema is known at compile time.
* `InsertOptions.UseSchemaCache`: when `true`, the full table schema is cached per (database, table) for the lifetime of the `ClickHouseClient` instance. Subsequent inserts to the same table reuse the cached schema regardless of which columns are selected, eliminating redundant round-trips.
**Breaking Changes:**
* `InsertBinaryAsync` now throws `InvalidOperationException` when sessions are enabled and `MaxDegreeOfParallelism > 1`. ClickHouse only allows one concurrent query per session, so parallel batch inserts would cause `SESSION_IS_LOCKED` errors and partial writes. This also affects the deprecated `ClickHouseBulkCopy`, which defaults to `MaxDegreeOfParallelism = 4`. To fix, set `MaxDegreeOfParallelism` to 1, or disable sessions for the insert via `InsertOptions.UseSession = false`.
**Bug Fixes:**
* Fixed `IndexOutOfRangeException` when reading NULL values from `Variant` columns. The Variant `None` discriminator (used for NULLs) was not handled, causing an out-of-bounds array access instead of returning `DBNull.Value`.
* Fixed writing NULL values to `Variant` columns. Writing null/DBNull now correctly emits the `None` discriminator (`0xFF`) for binary writes, and null marker `\N` when using HTTP parameters. Note: null Variant HTTP parameter parsing is broken in server versions prior to 26.3.
v1.0.2
---
**Bug Fixes:**
* Fixed `QUERY_WITH_SAME_ID_IS_ALREADY_RUNNING` errors when using `InsertBinaryAsync` with a `QueryId`. The schema probe and all batch inserts were sharing the same query ID. The schema probe now uses the base query ID, and each batch insert receives a unique suffixed ID (`{queryId}-1`, `{queryId}-2`, etc.).
v1.0.1
---
* Marked ClickHouseConnection.ServerVersion property as Obsolete.
v1.0.0
---
**Documentation and Usage Examples:**
Coinciding with the 1.0.0 release of the driver, we have greatly expanded the documentation and usage examples.
* Documentation: https://clickhouse.com/docs/integrations/csharp
* Usage examples: https://github.com/ClickHouse/clickhouse-cs/tree/main/examples
---
**New: ClickHouseClient - Simplified Primary API**
`ClickHouseClient` is the new recommended way to interact with ClickHouse. Thread-safe, singleton-friendly, and simpler than ADO.NET classes.
```csharp
using var client = new ClickHouseClient("Host=localhost");
```
| Method | Description |
|--------|--------------------------------------------------------------|
| `ExecuteNonQueryAsync` | Execute DDL/DML (CREATE, INSERT, ALTER, DROP) |
| `ExecuteScalarAsync` | Return first column of first row |
| `ExecuteReaderAsync` | Stream results via `ClickHouseDataReader` |
| `InsertBinaryAsync` | High-performance bulk insert (replaces `ClickHouseBulkCopy`) |
| `ExecuteRawResultAsync` | Get raw result stream bypassing the parser |
| `InsertRawStreamAsync` | Insert from stream (CSV, JSON, Parquet, etc.) |
| `PingAsync` | Check server connectivity |
| `CreateConnection()` | Get `ClickHouseConnection` for ORM compatibility |
**Per-query configuration** via `QueryOptions`.
**Parameters** via `ClickHouseParameterCollection`:
```csharp
var parameters = new ClickHouseParameterCollection();
parameters.Add("id", 42UL);
await client.ExecuteReaderAsync("SELECT * FROM t WHERE id = {id:UInt64}", parameters);
```
**Deprecation:** `ClickHouseBulkCopy` is deprecated. Use `client.InsertBinaryAsync(table, columns, rows)` instead.
---
**Breaking Changes:**
* **Dropped support for .NET Framework and .NET Standard.** The library now targets only `net6.0`, `net8.0`, `net9.0`, and `net10.0`. Removed support for `net462`, `net48`, and `netstandard2.1`. If you are using .NET Framework, you will need to stay on the previous version or migrate to .NET 6.0+.
* **Removed feature discovery query from `OpenAsync`.** The connection's `OpenAsync()` method no longer executes `SELECT version()` to discover server capabilities. This makes connection opening instantaneous (no network round-trip) but removes the `SupportedFeatures` property from `ClickHouseConnection`. The `ServerVersion` property now throws `InvalidOperationException`.
**Migration guidance:** If you need to check the server version:
```csharp
using var reader = await connection.ExecuteReaderAsync("SELECT version()");
reader.Read();
var version = reader.GetString(0);
```
* **DateTime reading behavior changed for columns without explicit timezone.** Previously, `DateTime` columns without a timezone (e.g., `DateTime` vs `DateTime('Europe/Amsterdam')`) would use the server timezone (with `UseServerTimezone=true`) or client timezone to interpret the stored value. Now, these columns return `DateTime` with `Kind=Unspecified`, preserving the wall-clock time exactly as stored without making assumptions about timezone.
| Column Type | Old Behavior | New Behavior |
|-------------|--------------|--------------|
| `DateTime` (no timezone) | Returned with server/client timezone applied | `DateTime` with `Kind=Unspecified` |
| `DateTime('UTC')` | `DateTime` with `Kind=Utc` | `DateTime` with `Kind=Utc` (unchanged) |
| `DateTime('Europe/Amsterdam')` | `DateTime` with `Kind=Unspecified` | `DateTime` with `Kind=Unspecified` (unchanged). Reading as DateTimeOffset has correct offset applied. |
**Migration guidance:** If you need timezone-aware behavior, either:
1. Use explicit timezones in your column definitions: `DateTime('UTC')` or `DateTime('Europe/Amsterdam')`
2. Apply the timezone yourself after reading.
* **DateTime writing now respects `DateTime.Kind` property.** Previously, all `DateTime` values were treated as wall-clock time in the target column's timezone regardless of their `Kind` property. The new behavior:
| DateTime.Kind | Old Behavior | New Behavior |
|---------------|--------------|--------------|
| `Utc` | Treated as wall-clock time in column timezone | Preserved as-is (instant is maintained) |
| `Local` | Treated as wall-clock time in column timezone | Instant is maintained (inserted as UTC timestamp) |
| `Unspecified` | Treated as wall-clock time in column timezone | Treated as wall-clock time in column timezone (unchanged) |
Migration guidance: If you were relying on the old behavior where UTC `DateTime` values were reinterpreted in the column timezone, you should change these to `DateTimeKind.Unspecified`:
```csharp
// Old code (worked by accident):
var utcTime = DateTime.UtcNow; // Would be reinterpreted in column timezone
// New code (explicit intent):
var wallClockTime = DateTime.SpecifyKind(myTime, DateTimeKind.Unspecified);
```
**Important:** When using parameters, you must specify the timezone in the parameter type hint to have string values interpreted in the column timezone:
```csharp
command.AddParameter("dt", myDateTime);
// Correct: timezone in type hint ensures proper interpretation
command.CommandText = "INSERT INTO table (dt_column) VALUES ({dt:DateTime('Europe/Amsterdam')})";
// Gotcha: without timezone hint, UTC is used for interpretation
command.CommandText = "INSERT INTO table (dt_column) VALUES ({dt:DateTime})";
// ^ String value interpreted in UTC, not column timezone!
```
This differs from bulk copy operations where the column timezone is known and used automatically.
* **Removed `UseServerTimezone` setting.** This setting has been removed from the connection string, `ClickHouseClientSettings`, and `ClickHouseConnectionStringBuilder`. It no longer has any effect since columns without timezones now return `Unspecified` DateTime values without any timezone changes applied to what is returned from the server.
* **Moved `ServerTimezone` property from `ClickHouseConnection` to `ClickHouseCommand`.** The server timezone is now available on `ClickHouseCommand.ServerTimezone` after any query execution (the timezone is now extracted from the `X-ClickHouse-Timezone` response header instead of requiring a separate query).
* **Helper and extension methods made internal:** DateTimeConversions, DataReaderExtensions, DictionaryExtensions, EnumerableExtensions, MathUtils, StringExtensions.
* **JSON writing default behavior changed.** The default `JsonWriteMode` has changed from `Binary` to `String`. This affects how JSON data is written to ClickHouse:
| Input Type | Old Default (Binary) | New Default (String) |
|------------|---------------------|----------------------|
| `JsonObject` / `JsonNode` | Binary encoding | Serialized via `JsonSerializer.Serialize()` |
| `string` | Binary encoding (parsed client-side) | Passed through directly |
| POCO (registered) | Binary encoding with type hints | Serialized via `JsonSerializer.Serialize()` |
| POCO (unregistered) | Exception | Serialized via `JsonSerializer.Serialize()` |
**Impact if you don't modify your code:**
- JSON writing will still work, but uses string serialization instead of binary encoding; the JSON string will be parsed on the server instead of the client. This could lead to subtle changes in paths without type hints, e.g., values previously parsed as ints may be parsed as longs.
- `ClickHouseJsonPath` and `ClickHouseJsonIgnore` attributes are ignored in String mode (they only work in Binary mode). Serialization happens via `System.Text.Json`, so you can use those attributes instead.
- Server setting `input_format_binary_read_json_as_string=1` is automatically set when using String write mode
**New Features/Improvements:**
* **Automatic parameter type extraction from SQL.** Types specified in the SQL query using `{name:Type}` syntax are now automatically used for parameter formatting, eliminating the need to specify the type twice:
```csharp
// Before: type specified twice
command.CommandText = "SELECT {dt:DateTime('Europe/Amsterdam')}";
command.AddParameter("dt", "DateTime('Europe/Amsterdam')", value);
// After: type extracted from SQL automatically
command.CommandText = "SELECT {dt:DateTime('Europe/Amsterdam')}";
command.AddParameter("dt", value);
```
The `AddParameter(name, type, value)` overload is now marked obsolete. Use `AddParameterWithTypeOverride()` if you need to explicitly override the SQL type hint.
* **POCO serialization support for JSON columns.** When writing POCOs to JSON columns with typed hints (e.g., `JSON(id Int64, name String)`), the driver serializes properties using the hinted types for full type fidelity. Properties without a corresponding hinted path will have their ClickHouse types inferred automatically. Two attributes are available: `[ClickHouseJsonPath("path")]` for custom JSON paths and `[ClickHouseJsonIgnore]` to exclude properties. Property name matching to hint paths is case-sensitive (matching ClickHouse behavior which allows paths like `userName` and `UserName` to coexist). Register types via `client.RegisterJsonSerializationType<T>()`.
* **`JsonReadMode` and `JsonWriteMode` connection string settings** for configurable JSON handling:
- `JsonReadMode.Binary` (default): Returns `System.Text.Json.Nodes.JsonObject`
- `JsonReadMode.String`: Returns raw JSON string. Sets server setting `output_format_binary_write_json_as_string=1`.
- `JsonWriteMode.String` (default): Accepts `JsonObject`, `JsonNode`, strings, and any object (serialized via `System.Text.Json.JsonSerializer`). Sets server setting `input_format_binary_read_json_as_string=1`.
- `JsonWriteMode.Binary`: Only accepts registered POCO types with full type hint support and custom path attributes. Writing `string` or `JsonNode` values with `JsonWriteMode.Binary` throws an exception.
* **QBit data type support.** QBit is a transposed vector column, designed to allow the user to choose a desired quantization level at runtime, speeding up approximate similarity searches. See the GitHub repo for usage examples.
* **Dynamic type binary writing support** via `InsertBinaryAsync`. Values are automatically type-inferred from their .NET types and serialized with the appropriate binary type header. Supports all common types including integers, floating point, strings, booleans, DateTime, Guid, decimal, arrays, lists, and dictionaries.
* **Binary data in String/FixedString columns.** Write `byte[]`, `ReadOnlyMemory<byte>`, or `Stream` values to String and FixedString columns via `InsertBinaryAsync`. Read binary data back using the `ReadStringsAsByteArrays` connection string setting, which returns String columns as `byte[]` instead of `string`. Useful for storing binary data that may not be valid UTF-8.
* **First-class support for roles**, with query-level override.
* **Custom HTTP headers** at the connection level for proxy/infrastructure integration.
* **Support for JWT authentication**, with query-level override.
* **Mid-stream exception detection** via `X-ClickHouse-Exception-Tag` header (ClickHouse 25.11+). When `http_write_exception_in_output_format` is set to 0 on the server, exceptions that occur while streaming results are now properly detected and thrown as `ClickHouseServerException` (which includes the exception message) instead of `EndOfStreamException`.
* **Query ID auto-generation.** When the query ID has not been set, it will now be automatically generated by the client.
* **`AddParameter()` convenience method** for `ClickHouseParameterCollection`, simplifying parameter creation.
**Bug Fixes:**
* Fixed a crash when reading a Map with duplicate keys. The current behavior is to return only the last value for a given key.