ClickHouse C# client
The official C# client for connecting to ClickHouse. The client source code is available in the GitHub repository. Originally developed by Oleg V. Kozlyuk.
Migration guide
- Update your
.csprojfile with the new package nameClickHouse.Driverand the latest version on NuGet. - Update all
ClickHouse.Clientreferences toClickHouse.Driverin your codebase.
Supported .NET versions
ClickHouse.Driver supports the following .NET versions:
- .NET Framework 4.6.2
- .NET Framework 4.8
- .NET Standard 2.1
- .NET 6.0
- .NET 8.0
- .NET 9.0
- .NET 10.0
Installation
Install the package from NuGet:
Or using the NuGet Package Manager:
Quick start
Configuration
There are two ways of configuring your connection to ClickHouse:
- Connection string: Semicolon-separated key/value pairs that specify the host, authentication credentials, and other connection options.
ClickHouseClientSettingsobject: A strongly typed configuration object that can be loaded from configuration files or set in code.
Below is a full list of all the settings, their default values, and their effects.
Connection settings
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| Host | string | "localhost" | Host | Hostname or IP address of the ClickHouse server |
| Port | ushort | 8123 (HTTP) / 8443 (HTTPS) | Port | Port number; defaults based on protocol |
| Username | string | "default" | Username | Authentication username |
| Password | string | "" | Password | Authentication password |
| Database | string | "" | Database | Default database; empty uses server/user default |
| Protocol | string | "http" | Protocol | Connection protocol: "http" or "https" |
| Path | string | null | Path | URL path for reverse proxy scenarios (e.g., /clickhouse) |
| Timeout | TimeSpan | 2 minutes | Timeout | Operation timeout (stored as seconds in connection string) |
Data format & serialization
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| UseCompression | bool | true | Compression | Enable gzip compression for data transfer |
| UseServerTimezone | bool | true | UseServerTimezone | Use server timezone for DateTime conversion; if false, uses local timezone |
| UseCustomDecimals | bool | true | UseCustomDecimals | Use ClickHouseDecimal for arbitrary precision; if false, uses .NET decimal (128-bit limit) |
| UseFormDataParameters | bool | false | UseFormDataParameters | Send parameters as form data instead of URL query string |
Session management
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| UseSession | bool | false | UseSession | Enable stateful sessions; serializes requests |
| SessionId | string | null | SessionId | Session ID; auto-generates GUID if null and UseSession is true |
The UseSession flag enables persistence of the server session, allowing use of SET statements and temporary tables. Sessions will be reset after 60 seconds of inactivity (default timeout). Session lifetime can be extended by setting session settings via ClickHouse statements or the server configuration.
The ClickHouseConnection class normally allows for parallel operation (multiple threads can run queries concurrently). However, enabling UseSession flag will limit that to one active query per connection at any moment of time (this is a server-side limitation).
Security
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| SkipServerCertificateValidation | bool | false | — | Skip HTTPS certificate validation; not for production use |
HTTP client configuration
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| HttpClient | HttpClient | null | — | Custom pre-configured HttpClient instance |
| HttpClientFactory | IHttpClientFactory | null | — | Custom factory for creating HttpClient instances |
| HttpClientName | string | null | — | Name for HttpClientFactory to create specific client |
Logging & debugging
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| LoggerFactory | ILoggerFactory | null | — | Logger factory for diagnostic logging |
| EnableDebugMode | bool | false | — | Enable .NET network tracing (requires LoggerFactory with level set to Trace); significant performance impact |
Custom settings & roles
| Property | Type | Default | Connection String Key | Description |
|---|---|---|---|---|
| CustomSettings | IDictionary<string, object> | Empty | set_* prefix | ClickHouse server settings, see note below |
| Roles | IReadOnlyList<string> | Empty | Roles | Comma-separated ClickHouse roles (e.g., Roles=admin,reader) |
When using a connection string to set custom settings, use the set_ prefix, e.g. "set_max_threads=4". When using a ClickHouseClientSettings object, do not use the set_ prefix.
For a full list of available settings, see here.
Connection string examples
Basic connection
With custom ClickHouse settings
Usage
Connecting
To connect to ClickHouse, create a ClickHouseConnection with a connection string or a ClickHouseClientSettings object. See the Configuration section for available options.
The details for your ClickHouse Cloud service are available in the ClickHouse Cloud console.
Select a service and click Connect:
Choose C#. Connection details are displayed below.
If you are using self-managed ClickHouse, the connection details are set by your ClickHouse administrator.
Using a connection string:
Or using ClickHouseClientSettings:
- A
ClickHouseConnectionrepresents a "session" with the server. It performs feature discovery by querying server version (so there is a minor overhead on opening), but generally it is safe to create and destroy such objects multiple times. - Recommended lifetime for a connection is one connection object per large "transaction" spanning multiple queries. The
ClickHouseConnectionobject can be long-lived. There is a minor overhead on connection startup, so it's not recommended to create a connection object for each query. - If an application operates on large volumes of transactions and requires to create/destroy
ClickHouseConnectionobjects often, it is recommended to useIHttpClientFactoryor a static instance ofHttpClientto manage connections.
Creating a table
Create a table using standard SQL syntax:
Inserting data
Insert data using parameterized queries:
Bulk insert
Use ClickHouseBulkCopy for inserting large numbers of rows. It streams data efficiently using ClickHouse's native row binary format, works in parallel, and can split the data into batches. It also avoids limitations with large parameter sets causing "URL too long" errors.
Using ClickHouseBulkCopy requires:
- Target connection (
ClickHouseConnectioninstance) - Target table name (
DestinationTableNameproperty) - Data source (
IDataReaderorIEnumerable<object[]>)
- For optimal performance, ClickHouseBulkCopy uses the Task Parallel Library (TPL) to process batches of data, with up to 4 parallel insertion tasks (this can be tuned).
- Column names can be optionally provided via
ColumnNamesproperty if source data has fewer columns than target table. - Configurable parameters:
Columns,BatchSize,MaxDegreeOfParallelism. - Before copying, a
SELECT * FROM <table> LIMIT 0query is performed to get information about target table structure. Types of provided objects must reasonably match the target table. - Sessions are not compatible with parallel insertion. Connection passed to
ClickHouseBulkCopymust have sessions disabled, orMaxDegreeOfParallelismmust be set to1.
Performing SELECT queries
Execute SELECT queries using ExecuteReader() or ExecuteReaderAsync(). The returned DbDataReader provides typed access to result columns via methods like GetInt64(), GetString(), and GetFieldValue<T>().
Call Read() to advance to the next row. It returns false when there are no more rows. Access columns by index (0-based) or by column name.
SQL parameters
In ClickHouse, the standard format for query parameters in SQL queries is {parameter_name:DataType}.
Examples:
SQL 'bind' parameters are passed as HTTP URI query parameters, so using too many of them may result in a "URL too long" exception. Using ClickHouseBulkInsert can bypass this limitation.
Query ID
Every method that makes a query will also include a query_id in the result. This unique identifier is assigned by the client per query and can be used to fetch data from the system.query_log table (if it is enabled), or cancel long-running queries. If necessary, the query ID can be overridden by the user in the ClickHouseCommand object.
If you are overriding the QueryId parameter, you need to ensure its uniqueness for every call. A random GUID is a good choice.
Raw streaming
It's possible to stream data in a particular format directly, bypassing the data reader. This can be useful in situations where you want to save the data to file in a particular format. For example:
Raw stream insert
Use InsertRawStreamAsync to insert data directly from file or memory streams in formats like CSV, JSON, or any supported ClickHouse format.
Insert from a CSV file:
See the format settings documentation for options to control data ingestion behavior.
More examples
For additional practical usage examples, see the examples directory in the GitHub repository.
Best practices
Connection lifetime and pooling
ClickHouse.Driver uses System.Net.Http.HttpClient under the hood. HttpClient has a per-endpoint connection pool. As a consequence:
- A
ClickHouseConnectionobject does not have 1:1 mapping to TCP connections - multiple database sessions will be multiplexed through several TCP connections per server. ClickHouseConnectionobjects can be long-lived; the actual TCP connections underneath will be recycled by the connection pool.- Let
HttpClientmanage connection pooling internally. Do not poolClickHouseConnectionobjects yourself. - Connections can stay alive after
ClickHouseConnectionobject was disposed. - This behavior can be tweaked by passing a custom
HttpClientFactoryorHttpClientwith customHttpClientHandler.
For DI environments, there is a bespoke constructor ClickHouseConnection(string connectionString, IHttpClientFactory httpClientFactory, string httpClientName = "") which makes the ClickHouseConnection request a named http client.
When using a custom HttpClient or HttpClientFactory, ensure that the PooledConnectionIdleTimeout is set to a value smaller than the server's keep_alive_timeout, in order to avoid errors due to half-closed connections. The default keep_alive_timeout for Cloud deployments is 10 seconds.
DateTime handling
-
Use UTC whenever possible. Store timestamps as
DateTime('UTC')columns and useDateTimeKind.Utcin your code. This eliminates timezone ambiguity. -
Use
DateTimeOffsetfor explicit timezone handling. It always represents a specific instant and includes the offset information. -
Specify timezone in HTTP parameter type hints. When using parameters with
UnspecifiedDateTime values targeting non-UTC columns:
Async inserts
Async inserts shift batching responsibility from the client to the server. Instead of requiring client-side batching, the server buffers incoming data and flushes it to storage based on configurable thresholds. This is useful for high-concurrency scenarios like observability workloads where many agents send small payloads.
Enable async inserts via CustomSettings or the connection string:
Two modes (controlled by wait_for_async_insert):
| Mode | Behavior | Use case |
|---|---|---|
wait_for_async_insert=1 | Insert returns after data is flushed to disk. Errors are returned to the client. | Recommended for most workloads |
wait_for_async_insert=0 | Insert returns immediately when data is buffered. No guarantee data will be persisted. | Only when data loss is acceptable |
With wait_for_async_insert=0, errors only surface during flush and cannot be traced back to the original insert. The client also provides no backpressure, risking server overload.
Key settings:
| Setting | Description |
|---|---|
async_insert_max_data_size | Flush when buffer reaches this size (bytes) |
async_insert_busy_timeout_ms | Flush after this timeout (milliseconds) |
async_insert_max_query_number | Flush after this many queries accumulate |
Sessions
Only enable sessions when you need stateful server-side features, e.g.:
- Temporary tables (
CREATE TEMPORARY TABLE) - Maintaining query context across multiple statements
- Session-level settings (
SET max_threads = 4)
When sessions are enabled, requests are serialized to prevent concurrent use of the same session. This adds overhead for workloads that don't require session state.
Supported data types
ClickHouse.Driver supports all ClickHouse data types. The tables below show the mappings between ClickHouse types and native .NET types when reading data from the database.
Type mapping: reading from ClickHouse
Integer types
| ClickHouse Type | .NET Type |
|---|---|
| Int8 | sbyte |
| UInt8 | byte |
| Int16 | short |
| UInt16 | ushort |
| Int32 | int |
| UInt32 | uint |
| Int64 | long |
| UInt64 | ulong |
| Int128 | BigInteger |
| UInt128 | BigInteger |
| Int256 | BigInteger |
| UInt256 | BigInteger |
Floating point types
| ClickHouse Type | .NET Type |
|---|---|
| Float32 | float |
| Float64 | double |
| BFloat16 | float |
Decimal types
| ClickHouse Type | .NET Type |
|---|---|
| Decimal(P, S) | decimal / ClickHouseDecimal |
| Decimal32(S) | decimal / ClickHouseDecimal |
| Decimal64(S) | decimal / ClickHouseDecimal |
| Decimal128(S) | decimal / ClickHouseDecimal |
| Decimal256(S) | decimal / ClickHouseDecimal |
Decimal type conversion is controlled via the UseCustomDecimals setting.
Boolean type
| ClickHouse Type | .NET Type |
|---|---|
| Bool | bool |
String types
| ClickHouse Type | .NET Type |
|---|---|
| String | string |
| FixedString(N) | byte[] |
Date and time types
| ClickHouse Type | .NET Type |
|---|---|
| Date | DateTime |
| Date32 | DateTime |
| DateTime | DateTime |
| DateTime32 | DateTime |
| DateTime64 | DateTime |
| Time | TimeSpan |
| Time64 | TimeSpan |
ClickHouse stores DateTime and DateTime64 values internally as Unix timestamps (seconds or sub-second units since epoch). While the storage is always in UTC, columns can have an associated timezone that affects how values are displayed and interpreted.
When reading DateTime values, the DateTime.Kind property is set based on the column's timezone:
| Column Timezone | Returned DateTime.Kind |
|---|---|
| UTC | DateTimeKind.Utc |
| Any other timezone | DateTimeKind.Unspecified |
| No timezone specified | DateTimeKind.Unspecified |
For non-UTC columns, the returned DateTime represents the wall-clock time in that timezone. Use ClickHouseDataReader.GetDateTimeOffset() to get a DateTimeOffset with the correct offset for that timezone:
UseServerTimezone setting
For columns without an explicit timezone (i.e., DateTime instead of DateTime('Europe/Amsterdam')), the UseServerTimezone connection string setting controls which timezone is used:
UseServerTimezone | Timezone Used | Default |
|---|---|---|
true | Server's timezone (from X-ClickHouse-Timezone header) | Yes |
false | Client's system timezone | No |
When a column has an explicit timezone, that timezone is always used regardless of this setting.
| Column Definition | UseServerTimezone=true | UseServerTimezone=false |
|---|---|---|
DateTime('UTC') | UTC, Kind=Utc | UTC, Kind=Utc |
DateTime('Europe/Amsterdam') | Amsterdam, Kind=Unspecified | Amsterdam, Kind=Unspecified |
DateTime (no tz) | Server timezone, Kind=Unspecified | Client timezone, Kind=Unspecified |
Other types
| ClickHouse Type | .NET Type |
|---|---|
| UUID | Guid |
| IPv4 | IPAddress |
| IPv6 | IPAddress |
| Nothing | DBNull |
| Dynamic | See note |
| Json | JsonObject |
| Array(T) | T[] |
| Tuple(T1, T2, ...) | Tuple<T1, T2, ...> / LargeTuple |
| Map(K, V) | Dictionary<K, V> |
| Nullable(T) | T? |
| Enum8 | string |
| Enum16 | string |
| LowCardinality(T) | Same as T |
| SimpleAggregateFunction | Same as underlying type |
| Nested(...) | Tuple[] |
| Variant(T1, T2, ...) | See note |
| QBit(T, dimension) | T[] |
The Dynamic and Variant types will be converted to the corresponding type for the actual underlying type in each row.
Geometry types
| ClickHouse Type | .NET Type |
|---|---|
| Point | Tuple<double, double> |
| Ring | Tuple<double, double>[] |
| LineString | Tuple<double, double>[] |
| Polygon | Ring[] |
| MultiLineString | LineString[] |
| MultiPolygon | Polygon[] |
| Geometry | See note |
The Geometry type is a Variant type that can hold any of the geometry types. It will be converted to the corresponding type.
Type mapping: writing to ClickHouse
When inserting data, the driver converts .NET types to their corresponding ClickHouse types. The tables below show which .NET types are accepted for each ClickHouse column type.
Integer types
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| Int8 | sbyte, any Convert.ToSByte() compatible | |
| UInt8 | byte, any Convert.ToByte() compatible | |
| Int16 | short, any Convert.ToInt16() compatible | |
| UInt16 | ushort, any Convert.ToUInt16() compatible | |
| Int32 | int, any Convert.ToInt32() compatible | |
| UInt32 | uint, any Convert.ToUInt32() compatible | |
| Int64 | long, any Convert.ToInt64() compatible | |
| UInt64 | ulong, any Convert.ToUInt64() compatible | |
| Int128 | BigInteger, decimal, double, float, int, uint, long, ulong, any Convert.ToInt64() compatible | |
| UInt128 | BigInteger, decimal, double, float, int, uint, long, ulong, any Convert.ToInt64() compatible | |
| Int256 | BigInteger, decimal, double, float, int, uint, long, ulong, any Convert.ToInt64() compatible | |
| UInt256 | BigInteger, decimal, double, float, int, uint, long, ulong, any Convert.ToInt64() compatible |
Floating point types
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| Float32 | float, any Convert.ToSingle() compatible | |
| Float64 | double, any Convert.ToDouble() compatible | |
| BFloat16 | float, any Convert.ToSingle() compatible | Truncates to 16-bit brain float format |
Boolean type
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| Bool | bool |
String types
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| String | string, any Convert.ToString() compatible | |
| FixedString(N) | string, byte[] | String is UTF-8 encoded and padded/truncated; byte[] must be exactly N bytes |
Date and time types
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| Date | DateTime, DateTimeOffset, DateOnly, NodaTime types | Converted to Unix days as UInt16 |
| Date32 | DateTime, DateTimeOffset, DateOnly, NodaTime types | Converted to Unix days as Int32 |
| DateTime | DateTime, DateTimeOffset, DateOnly, NodaTime types | See below for details |
| DateTime32 | DateTime, DateTimeOffset, DateOnly, NodaTime types | Same as DateTime |
| DateTime64 | DateTime, DateTimeOffset, DateOnly, NodaTime types | Precision based on Scale parameter |
| Time | TimeSpan, int | Clamped to ±999:59:59; int treated as seconds |
| Time64 | TimeSpan, decimal, double, float, int, long, string | String parsed as [-]HHH:MM:SS[.fraction]; clamped to ±999:59:59.999999999 |
The driver respects DateTime.Kind when writing values:
DateTime.Kind | Behavior |
|---|---|
Utc | Instant is preserved exactly |
Local | Converted to UTC using system timezone, instant preserved |
Unspecified | Treated as wall-clock time in target column's timezone |
DateTimeOffset values always preserve the exact instant.
Example: UTC DateTime (instant preserved)
Example: unspecified DateTime (wall-clock time)
Recommendation: for simplest and most predictable behavior, use DateTimeKind.Utc or DateTimeOffset for all DateTime operations. This ensures your code works consistently regardless of server timezone, client timezone, or column timezone.
HTTP parameters vs bulk copy
There is an important difference between HTTP parameter binding and bulk copy when writing Unspecified DateTime values:
Bulk Copy knows the target column's timezone and correctly interprets Unspecified values in that timezone.
HTTP Parameters do not automatically know the column timezone. You must specify it in the parameter type hint:
DateTime.Kind | Target Column | HTTP Param (with tz hint) | HTTP Param (no tz hint) | Bulk Copy |
|---|---|---|---|---|
Utc | UTC | Instant preserved | Instant preserved | Instant preserved |
Utc | Europe/Amsterdam | Instant preserved | Instant preserved | Instant preserved |
Local | Any | Instant preserved | Instant preserved | Instant preserved |
Unspecified | UTC | Treated as UTC | Treated as UTC | Treated as UTC |
Unspecified | Europe/Amsterdam | Treated as Amsterdam time | Treated as UTC | Treated as Amsterdam time |
Decimal types
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| Decimal(P,S) | decimal, ClickHouseDecimal, any Convert.ToDecimal() compatible | Throws OverflowException if exceeds precision |
| Decimal32 | decimal, ClickHouseDecimal, any Convert.ToDecimal() compatible | Max precision 9 |
| Decimal64 | decimal, ClickHouseDecimal, any Convert.ToDecimal() compatible | Max precision 18 |
| Decimal128 | decimal, ClickHouseDecimal, any Convert.ToDecimal() compatible | Max precision 38 |
| Decimal256 | decimal, ClickHouseDecimal, any Convert.ToDecimal() compatible | Max precision 76 |
Other types
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| UUID | Guid, string | String parsed as Guid |
| IPv4 | IPAddress, string | Must be IPv4; string parsed via IPAddress.Parse() |
| IPv6 | IPAddress, string | Must be IPv6; string parsed via IPAddress.Parse() |
| Nothing | Any | Writes nothing (no-op) |
| Dynamic | — | Not supported (throws NotImplementedException) |
| Json | string, JsonObject, any object | String parsed as JSON; objects serialized via JsonSerializer |
| Array(T) | IList, null | Null writes empty array |
| Tuple(T1, T2, ...) | ITuple, IList | Element count must match tuple arity |
| Map(K, V) | IDictionary | |
| Nullable(T) | null, DBNull, or types accepted by T | Writes null flag byte before value |
| Enum8 | string, sbyte, numeric types | String looked up in enum dictionary |
| Enum16 | string, short, numeric types | String looked up in enum dictionary |
| LowCardinality(T) | Types accepted by T | Delegates to underlying type |
| SimpleAggregateFunction | Types accepted by underlying type | Delegates to underlying type |
| Nested(...) | IList of tuples | Element count must match field count |
| Variant(T1, T2, ...) | Value matching one of T1, T2, ... | Throws ArgumentException if no type match |
| QBit(T, dim) | IList | Delegates to Array; dimension is metadata only |
Geometry types
| ClickHouse Type | Accepted .NET Types | Notes |
|---|---|---|
| Point | System.Drawing.Point, ITuple, IList (2 elements) | |
| Ring | IList of Points | |
| LineString | IList of Points | |
| Polygon | IList of Rings | |
| MultiLineString | IList of LineStrings | |
| MultiPolygon | IList of Polygons | |
| Geometry | Any geometry type above | Variant of all geometry types |
Not supported for writing
| ClickHouse Type | Notes |
|---|---|
| Dynamic | Throws NotImplementedException |
| AggregateFunction | Throws AggregateFunctionException |
Nested type handling
ClickHouse nested types (Nested(...)) can be read and written using array semantics.
Logging and diagnostics
The ClickHouse .NET client integrates with the Microsoft.Extensions.Logging abstractions to offer lightweight, opt-in logging. When enabled, the driver emits structured messages for connection lifecycle events, command execution, transport operations, and bulk copy uploads. Logging is entirely optional—applications that do not configure a logger continue to run without additional overhead.
Quick start
Using ClickHouseConnection
Using appsettings.json
You can configure logging levels using standard .NET configuration:
Using in-memory configuration
You can also configure logging verbosity by category in code:
Categories and emitters
The driver uses dedicated categories so that you can fine-tune log levels per component:
| Category | Source | Highlights |
|---|---|---|
ClickHouse.Driver.Connection | ClickHouseConnection | Connection lifecycle, HTTP client factory selection, connection opening/closing, session management. |
ClickHouse.Driver.Command | ClickHouseCommand | Query execution start/completion, timing, query IDs, server statistics, and error details. |
ClickHouse.Driver.Transport | ClickHouseConnection | Low-level HTTP streaming requests, compression flags, response status codes, and transport failures. |
ClickHouse.Driver.BulkCopy | ClickHouseBulkCopy | Metadata loading, batch operations, row counts, and upload completions. |
ClickHouse.Driver.NetTrace | TraceHelper | Network tracing, only when debug mode is enabled |
Example: Diagnosing connection issues
This will log:
- HTTP client factory selection (default pool vs single connection)
- HTTP handler configuration (SocketsHttpHandler or HttpClientHandler)
- Connection pool settings (MaxConnectionsPerServer, PooledConnectionLifetime, etc.)
- Timeout settings (ConnectTimeout, Expect100ContinueTimeout, etc.)
- SSL/TLS configuration
- Connection open/close events
- Session ID tracking
Debug mode: network tracing and diagnostics
To help with diagnosing networking issues, the driver library includes a helper that enables low-level tracing of .NET networking internals. To enable it you must pass a LoggerFactory with the level set to Trace, and set EnableDebugMode to true (or manually enable it via the ClickHouse.Driver.Diagnostic.TraceHelper class). Events will be logged to the ClickHouse.Driver.NetTrace category. Warning: this will generate extremely verbose logs, and impact performance. It is not recommended to enable debug mode in production.
OpenTelemetry
The driver provides built-in support for OpenTelemetry distributed tracing via the .NET System.Diagnostics.Activity API. When enabled, the driver emits spans for database operations that can be exported to observability backends like Jaeger or ClickHouse itself (via the OpenTelemetry Collector).
Enabling tracing
In ASP.NET Core applications, add the ClickHouse driver's ActivitySource to your OpenTelemetry configuration:
For console applications, testing, or manual setup:
Span attributes
Each span includes standard OpenTelemetry database attributes plus ClickHouse-specific query statistics that can be used for debugging.
| Attribute | Description |
|---|---|
db.system | Always "clickhouse" |
db.name | Database name |
db.user | Username |
db.statement | SQL query (if enabled) |
db.clickhouse.read_rows | Rows read by the query |
db.clickhouse.read_bytes | Bytes read by the query |
db.clickhouse.written_rows | Rows written by the query |
db.clickhouse.written_bytes | Bytes written by the query |
db.clickhouse.elapsed_ns | Server-side execution time in nanoseconds |
Configuration options
Control tracing behavior via ClickHouseDiagnosticsOptions:
Enabling IncludeSqlInActivityTags may expose sensitive data in your traces. Use with caution in production environments.
TLS configuration
When connecting to ClickHouse over HTTPS, you can configure TLS/SSL behavior in several ways.
Custom certificate validation
For production environments requiring custom certificate validation logic, provide your own HttpClient with a configured ServerCertificateCustomValidationCallback handler:
Important considerations when providing a custom HttpClient
- Automatic decompression: You must enable
AutomaticDecompressionif compression is not disabled (compression is enabled by default). - Idle timeout: Set
PooledConnectionIdleTimeoutsmaller than the server'skeep_alive_timeout(10 seconds for ClickHouse Cloud) to avoid connection errors from half-open connections.
ORM support
Dapper
ClickHouse.Driver can be used with Dapper, but anonymous objects are not supported.
Working example:
Not supported:
Linq2db
This driver is compatible with linq2db, a lightweight ORM and LINQ provider for .NET. See the project website for detailed documentation.
Example usage:
Create a DataConnection using the ClickHouse provider:
Table mappings can be defined using attributes or fluent configuration. If your class and property names match the table and column names exactly, no configuration is needed:
Querying:
Bulk Copy:
Use BulkCopyAsync for efficient bulk inserts.
Entity framework core
Entity Framework Core is currently not supported.
Limitations
AggregateFunction columns
Columns of type AggregateFunction(...) cannot be queried or inserted directly.
To insert:
To select: