Перейти к основному содержимому
Перейти к основному содержимому

Клиент ClickHouse для C#

Официальный клиент C# для подключения к ClickHouse. Исходный код клиента доступен в репозитории GitHub. Изначально разработан Oleg V. Kozlyuk.

Библиотека предоставляет два основных API:

  • ClickHouseClient (рекомендуется): высокоуровневый, потокобезопасный клиент, предназначенный для использования в виде singleton. Предоставляет простой асинхронный API для выполнения запросов и пакетных вставок. Наиболее подходящий вариант для большинства приложений.

  • ADO.NET (ClickHouseDataSource, ClickHouseConnection, ClickHouseCommand): стандартные абстракции базы данных в .NET. Необходимы для интеграции с ORM (Dapper, Linq2db) и в случаях, когда требуется совместимость с ADO.NET. ClickHouseBulkCopy — вспомогательный класс для эффективной вставки данных с использованием подключения ADO.NET. ClickHouseBulkCopy объявлен устаревшим и будет удалён в одном из будущих релизов; вместо него используйте ClickHouseClient.InsertBinaryAsync.

Оба API используют общий пул HTTP‑подключений и могут использоваться вместе в одном приложении.

Руководство по миграции

  1. Обновите файл .csproj, указав новое имя пакета ClickHouse.Driver и последнюю версию на NuGet.
  2. Замените все вхождения ClickHouse.Client на ClickHouse.Driver в вашей кодовой базе.

Поддерживаемые версии .NET

ClickHouse.Driver поддерживает следующие версии .NET:

  • .NET Framework 4.6.2
  • .NET Framework 4.8
  • .NET Standard 2.1
  • .NET 6.0
  • .NET 8.0
  • .NET 9.0
  • .NET 10.0

Установка

Установите пакет из NuGet:

dotnet add package ClickHouse.Driver

Или с помощью менеджера пакетов NuGet:

Install-Package ClickHouse.Driver

Быстрый старт

using ClickHouse.Driver;

// Create a client (typically as a singleton)
using var client = new ClickHouseClient("Host=my.clickhouse;Protocol=https;Port=8443;Username=user");

// Execute a query
var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine(version);

Конфигурация

Существует два способа настройки подключения к ClickHouse:

  • Строка подключения: Пары ключ/значение, разделённые точкой с запятой, которые задают хост, учётные данные для аутентификации и другие параметры подключения.
  • Объект ClickHouseClientSettings: Строго типизированный объект конфигурации, который может быть загружен из файлов конфигурации или задан в коде.

Ниже приведён полный список всех параметров, их значений по умолчанию и того, как они влияют на подключение.

Параметры подключения

СвойствоТипЗначение по умолчаниюКлюч строки подключенияОписание
Hoststring"localhost"HostИмя хоста или IP-адрес сервера ClickHouse
Portushort8123 (HTTP) / 8443 (HTTPS)PortНомер порта; по умолчанию выбирается в зависимости от протокола
Usernamestring"default"UsernameИмя пользователя для аутентификации
Passwordstring""PasswordПароль для аутентификации
Databasestring""DatabaseБаза данных по умолчанию; если не задано, используется значение по умолчанию сервера/пользователя
Protocolstring"http"ProtocolПротокол подключения: "http" или "https"
PathstringnullPathПуть в URL для сценариев с обратным прокси (например, /clickhouse)
TimeoutTimeSpan2 минутыTimeoutТаймаут операции (в строке подключения хранится в секундах)

Формат данных и сериализация

СвойствоТипПо умолчаниюКлюч строки подключенияОписание
UseCompressionbooltrueCompressionВключить сжатие gzip при передаче данных
UseCustomDecimalsbooltrueUseCustomDecimalsИспользовать ClickHouseDecimal для произвольной точности; если false, используется .NET decimal (ограничение 128 бит)
ReadStringsAsByteArraysboolfalseReadStringsAsByteArraysЧитать столбцы String и FixedString как массивы байтов byte[] вместо строк string; полезно для двоичных данных
UseFormDataParametersboolfalseUseFormDataParametersОтправлять параметры в виде form data вместо URL-строки запроса
JsonReadModeJsonReadModeBinaryJsonReadModeКак возвращаются JSON-данные: Binary (возвращает JsonObject) или String (возвращает исходную строку JSON)
JsonWriteModeJsonWriteModeStringJsonWriteModeКак отправляются JSON-данные: String (сериализует через JsonSerializer, принимает любые входные данные) или Binary (только зарегистрированные объекты POCO с подсказками типов)

Управление сессиями

СвойствоТипЗначение по умолчаниюКлюч строки подключенияОписание
UseSessionboolfalseUseSessionВключить состояние сессий; выполняет запросы последовательно
SessionIdstringnullSessionIdИдентификатор сессии; автоматически генерирует GUID, если null и UseSession имеет значение true
Примечание

Флаг UseSession включает сохранение серверной сессии, что позволяет использовать операторы SET и временные таблицы. Сессии будут сброшены после 60 секунд бездействия (тайм-аут по умолчанию). Время жизни сессии можно увеличить, задав параметры сессии с помощью операторов ClickHouse или конфигурации сервера.

Класс ClickHouseConnection обычно поддерживает параллельную работу (несколько потоков могут выполнять запросы одновременно). Однако включение флага UseSession ограничит выполнение одним активным запросом на соединение в любой момент времени (это ограничение на стороне сервера).

Безопасность

СвойствоТипЗначение по умолчаниюКлюч строки подключенияОписание
SkipServerCertificateValidationboolfalseОтключить проверку HTTPS-сертификата; не использовать в продуктивной среде

Конфигурация HTTP‑клиента

СвойствоТипЗначение по умолчаниюКлюч строки подключенияОписание
HttpClientHttpClientnullПользовательский предварительно настроенный экземпляр HttpClient
HttpClientFactoryIHttpClientFactorynullПользовательская фабрика для создания экземпляров HttpClient
HttpClientNamestringnullИмя, используемое HttpClientFactory для создания конкретного клиента

Логирование и отладка

СвойствоТипЗначение по умолчаниюКлюч строки подключенияОписание
LoggerFactoryILoggerFactorynullФабрика логгеров для диагностического логирования
EnableDebugModeboolfalseВключить .NET network tracing (требуется LoggerFactory с уровнем, установленным на Trace); значительное влияние на производительность

Пользовательские настройки и роли

СвойствоТипЗначение по умолчаниюКлюч строки подключенияОписание
CustomSettingsIDictionary<string, object>Пустопрефикс set_*Настройки сервера ClickHouse, см. примечание ниже.
RolesIReadOnlyList<string>ПустоRolesРоли ClickHouse, перечисленные через запятую (например, Roles=admin,reader)
Примечание

При использовании строки подключения для задания пользовательских настроек добавляйте префикс set_, например «set_max_threads=4». При использовании объекта ClickHouseClientSettings префикс set_ добавлять не нужно.

Полный список доступных настроек см. здесь.


Примеры строк подключения

Простое подключение

Host=localhost;Port=8123;Username=default;Password=secret;Database=mydb

С пользовательскими настройками ClickHouse

Host=localhost;set_max_threads=4;set_readonly=1;set_max_memory_usage=10000000000

QueryOptions

QueryOptions позволяет переопределять клиентские настройки для отдельных запросов. Все свойства являются необязательными и переопределяют значения по умолчанию клиента только если они заданы.

PropertyTypeDescription
QueryIdstringПользовательский идентификатор запроса для отслеживания в system.query_log или отмены
DatabasestringПереопределяет базу данных по умолчанию для этого запроса
RolesIReadOnlyList<string>Переопределяет роли клиента для этого запроса
CustomSettingsIDictionary<string, object>Настройки сервера ClickHouse для этого запроса (например, max_threads)
CustomHeadersIDictionary<string, string>Дополнительные HTTP‑заголовки для этого запроса
UseSessionbool?Переопределяет поведение сессии для этого запроса
SessionIdstringИдентификатор сессии для этого запроса (требуется UseSession = true)
BearerTokenstringПереопределяет токен аутентификации для этого запроса
MaxExecutionTimeTimeSpan?Тайм‑аут выполнения запроса на стороне сервера (передаётся как настройка max_execution_time); сервер отменит запрос при превышении

Пример:

var options = new QueryOptions
{
    QueryId = "report-2024-001",
    Database = "analytics",
    CustomSettings = new Dictionary<string, object>
    {
        { "max_threads", 4 },
        { "max_memory_usage", 10_000_000_000 }
    },
    MaxExecutionTime = TimeSpan.FromMinutes(5)
};

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

InsertOptions

InsertOptions расширяет QueryOptions настройками, предназначенными для пакетных операций вставки через InsertBinaryAsync.

СвойствоТипПо умолчаниюОписание
BatchSizeint100,000Количество строк в пакете
MaxDegreeOfParallelismint1Количество параллельных загрузок пакетов
FormatRowBinaryFormatRowBinaryБинарный формат: RowBinary или RowBinaryWithDefaults

Все свойства QueryOptions также доступны в InsertOptions.

Пример:

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

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

ClickHouseClient

ClickHouseClient — это рекомендуемый API для взаимодействия с ClickHouse. Он потокобезопасен, предназначен для использования как синглтон и самостоятельно управляет пулом HTTP‑соединений.

Создание клиента

Создайте ClickHouseClient, указав строку подключения, или используйте объект ClickHouseClientSettings. См. раздел Configuration с описанием доступных параметров.

Информация о вашем сервисе ClickHouse Cloud доступна в консоли ClickHouse Cloud.

Выберите сервис и нажмите Connect:

Кнопка подключения к сервису ClickHouse Cloud

Выберите C#. Ниже будут отображены параметры подключения.

Параметры подключения ClickHouse Cloud для C#

Если вы используете самоуправляемый ClickHouse, параметры подключения задаются вашим администратором ClickHouse.

Подключение с помощью строки подключения:

using ClickHouse.Driver;

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

Или, используя ClickHouseClientSettings:

using ClickHouse.Driver;

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

В сценариях внедрения зависимостей используйте IHttpClientFactory:

// In your DI configuration
services.AddHttpClient("ClickHouse", client =>
{
    client.Timeout = TimeSpan.FromMinutes(5);
}).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
});

// Create client with factory
var factory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var client = new ClickHouseClient("Host=localhost", factory, "ClickHouse");
Примечание

ClickHouseClient спроектирован как долгоживущий объект, который используется совместно во всём приложении. Создайте его один раз (обычно как singleton) и переиспользуйте для всех операций с базой данных. Клиент самостоятельно управляет пулом HTTP‑подключений.


Выполнение запросов

Используйте ExecuteNonQueryAsync для команд, не возвращающих результат:

// Create a table
await client.ExecuteNonQueryAsync(
    "CREATE TABLE IF NOT EXISTS default.my_table (id Int64, name String) ENGINE = Memory"
);

// Drop a table
await client.ExecuteNonQueryAsync("DROP TABLE IF EXISTS default.my_table");

Используйте ExecuteScalarAsync, чтобы получить одно значение:

var count = await client.ExecuteScalarAsync("SELECT count() FROM default.my_table");
Console.WriteLine($"Row count: {count}");

var version = await client.ExecuteScalarAsync("SELECT version()");
Console.WriteLine($"Server version: {version}");

Вставка данных

Параметризованные вставки

Вставляйте данные, используя параметризованные запросы и метод ExecuteNonQueryAsync. Типы параметров должны быть указаны в SQL с использованием синтаксиса {name:Type}:

using ClickHouse.Driver;
using ClickHouse.Driver.ADO.Parameters;

var parameters = new ClickHouseParameterCollection();
parameters.AddParameter("id", 1L);
parameters.AddParameter("name", "Alice");

await client.ExecuteNonQueryAsync(
    "INSERT INTO default.my_table (id, name) VALUES ({id:Int64}, {name:String})",
    parameters
);

Массовые вставки

Используйте InsertBinaryAsync для эффективной вставки большого количества строк. Он выполняет потоковую передачу данных в нативном двоичном формате строк ClickHouse, поддерживает параллельную пакетную загрузку и предотвращает ошибки «URL слишком длинный», которые могут возникать при использовании параметризованных запросов.

// Prepare data as IEnumerable<object[]>
var rows = Enumerable.Range(0, 1_000_000)
    .Select(i => new object[] { (long)i, $"value{i}" });

var columns = new[] { "id", "name" };

// Basic insert
long rowsInserted = await client.InsertBinaryAsync("default.my_table", columns, rows);
Console.WriteLine($"Rows inserted: {rowsInserted}");

Для больших наборов данных настройте пакетную вставку и параллелизм с помощью InsertOptions:

var options = new InsertOptions
{
    BatchSize = 100_000,           // Rows per batch (default: 100,000)
    MaxDegreeOfParallelism = 4     // Parallel batch uploads (default: 1)
};
Примечание
  • Клиент автоматически получает структуру таблицы с помощью SELECT * FROM <table> WHERE 1=0 перед вставкой. Передаваемые значения должны соответствовать типам целевых столбцов.
  • При MaxDegreeOfParallelism > 1 пакеты данных загружаются параллельно. Сеансы несовместимы с параллельной вставкой; либо отключите сеансы, либо установите MaxDegreeOfParallelism = 1.
  • Используйте RowBinaryFormat.RowBinaryWithDefaults в InsertOptions.Format, если вы хотите, чтобы сервер применял значения DEFAULT для столбцов, которые не были переданы.

Чтение данных

Используйте ExecuteReaderAsync для выполнения запросов SELECT. Возвращаемый ClickHouseDataReader предоставляет типизированный доступ к столбцам результата через методы, такие как GetInt64(), GetString() и GetFieldValue<T>().

Вызывайте Read(), чтобы перейти к следующей строке. Метод возвращает false, когда строк больше нет. Обращайтесь к столбцам по индексу (с нуля) или по имени столбца.

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)}");
}

Параметры SQL

В ClickHouse стандартный формат параметров в SQL-запросах — {parameter_name:DataType}.

Примеры:

SELECT {value:Array(UInt16)} as a
SELECT * FROM table WHERE val = {tuple_in_tuple:Tuple(UInt8, Tuple(String, UInt8))}
INSERT INTO table VALUES ({val1:Int32}, {val2:Array(UInt8)})
Примечание

Параметры привязки SQL (bind) передаются как параметры HTTP URI-запроса, поэтому при их чрезмерном количестве может возникнуть исключение «URL too long». Используйте InsertBinaryAsync для пакетной вставки данных, чтобы избежать этого ограничения.


Идентификатор запроса (Query ID)

Каждому запросу назначается уникальный query_id, который можно использовать для получения данных из таблицы system.query_log или прерывания длительно выполняющихся запросов. Вы можете указать собственный идентификатор запроса с помощью QueryOptions:

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

var reader = await client.ExecuteReaderAsync(
    "SELECT * FROM large_table",
    parameters: null,
    options: options
);
Совет

Если вы указываете собственный QueryId, убедитесь, что он уникален для каждого запроса. Случайный GUID — хороший выбор.


Необработанный стриминг

Используйте ExecuteRawResultAsync, чтобы передавать результаты запроса в определённом формате непосредственно, обходя data reader. Это полезно для экспорта данных в файлы или их передачи в другие системы:

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. Полный список вариантов см. в документации по форматам.


Вставка из необработанного потока

Используйте InsertRawStreamAsync, чтобы вставлять данные непосредственно из файловых потоков или потоков памяти в форматах, таких как CSV, JSON, Parquet или любой поддерживаемый формат ClickHouse.

Вставка из CSV‑файла:

await using var fileStream = File.OpenRead("data.csv");

using var response = await client.InsertRawStreamAsync(
    table: "my_table",
    stream: fileStream,
    format: "CSV",
    columns: ["id", "product", "price"] // Optional: specify columns
);
Примечание

См. документацию по настройкам форматов для получения сведений о параметрах, управляющих процессом ингестии данных.


Дополнительные примеры

См. дополнительные практические примеры использования в директории examples репозитория GitHub.

ADO.NET

Библиотека предоставляет полную поддержку ADO.NET через ClickHouseConnection, ClickHouseCommand и ClickHouseDataReader. Этот API необходим для интеграции с ORM (Dapper, Linq2db), а также когда вам нужны стандартные абстракции работы с базами данных в .NET.

Управление временем жизни с ClickHouseDataSource

Всегда создавайте подключения из ClickHouseDataSource, чтобы обеспечить корректное управление временем жизни и работу пула соединений. Этот источник данных внутренне управляет одним экземпляром ClickHouseClient, и все подключения используют его пул HTTP-соединений.

using ClickHouse.Driver.ADO;

// Create DataSource once (register as singleton in DI)
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default;Password=secret");

// Create lightweight connections as needed
await using var connection = await dataSource.OpenConnectionAsync();

// Use the connection
await using var command = connection.CreateCommand("SELECT version()");
var version = await command.ExecuteScalarAsync();

Для внедрения зависимостей:

// In Startup.cs or Program.cs
services.AddSingleton(sp =>
{
    var factory = sp.GetRequiredService<IHttpClientFactory>();
    return new ClickHouseDataSource("Host=localhost", factory, "ClickHouse");
});

// In your service
public class MyService
{
    private readonly ClickHouseDataSource _dataSource;

    public MyService(ClickHouseDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public async Task DoWorkAsync()
    {
        await using var connection = await _dataSource.OpenConnectionAsync();
        // Use connection...
    }
}
Примечание

Не создавайте ClickHouseConnection напрямую в продакшн-коде. Каждое такое создание инициализирует новый HTTP‑клиент и пул соединений, что под нагрузкой может привести к исчерпанию сокетов:

// DON'T DO THIS - creates new connection pool each time
using var conn = new ClickHouseConnection("Host=localhost");
await conn.OpenAsync();

Вместо этого всегда используйте ClickHouseDataSource или один общий экземпляр ClickHouseClient.


Использование ClickHouseCommand

Создавайте команды из подключения для выполнения SQL-запросов:

await using var connection = await dataSource.OpenConnectionAsync();

// Create command with SQL
await using var command = connection.CreateCommand("SELECT * FROM my_table WHERE id = {id:Int64}");
command.AddParameter("id", 42L);

// Execute and read results
await using var reader = await command.ExecuteReaderAsync();
while (reader.Read())
{
    Console.WriteLine($"Name: {reader.GetString("name")}");
}

Методы выполнения команд:

  • ExecuteNonQueryAsync() - для INSERT, UPDATE, DELETE и DDL-команд
  • ExecuteScalarAsync() - возвращает первый столбец первой строки
  • ExecuteReaderAsync() - возвращает ClickHouseDataReader для перебора результатов

Использование ClickHouseDataReader

ClickHouseDataReader предоставляет доступ к результатам запроса с сохранением типов:

await using var reader = await command.ExecuteReaderAsync();

while (reader.Read())
{
    // Access by column index
    var id = reader.GetInt64(0);
    var name = reader.GetString(1);

    // Access by column name
    var email = reader.GetString("email");

    // Generic access
    var timestamp = reader.GetFieldValue<DateTime>("created_at");

    // Check for null
    if (!reader.IsDBNull("optional_field"))
    {
        var value = reader.GetString("optional_field");
    }
}

Рекомендации

Время жизни соединения и пул подключений

ClickHouse.Driver внутренне использует System.Net.Http.HttpClient. HttpClient имеет пул подключений для каждой конечной точки (endpoint). В результате:

  • Сеансы работы с базой данных мультиплексируются через HTTP‑соединения, которыми управляет пул подключений.
  • HTTP‑соединения автоматически переиспользуются пулом.
  • Соединения могут оставаться активными после удаления объектов ClickHouseClient или ClickHouseConnection.

Рекомендуемые шаблоны использования:

СценарийРекомендуемый подход
Общий случайИспользовать синглтон ClickHouseClient
ADO.NET / ORMИспользовать ClickHouseDataSource (создаёт соединения, которые разделяют один и тот же пул)
DI‑окруженияРегистрировать ClickHouseClient или ClickHouseDataSource как синглтон с IHttpClientFactory
Ссылки

При использовании пользовательского HttpClient или HttpClientFactory убедитесь, что PooledConnectionIdleTimeout имеет значение меньше, чем keep_alive_timeout сервера, чтобы избежать ошибок из‑за наполовину закрытых соединений. Значение keep_alive_timeout по умолчанию для развертываний в Cloud — 10 секунд.

Примечание

Избегайте создания множества экземпляров ClickHouseClient или отдельных ClickHouseConnection без общего HttpClient. Каждый экземпляр создаёт собственный пул подключений.


Обработка DateTime

  1. По возможности используйте UTC. Храните метки времени в столбцах DateTime('UTC') и используйте DateTimeKind.Utc в коде. Это устраняет неоднозначность, связанную с часовыми поясами.

  2. Используйте DateTimeOffset для явной обработки часовых поясов. Он всегда представляет конкретный момент времени и включает информацию о смещении.

  3. Указывайте часовой пояс в подсказках типов SQL. При использовании параметров с Unspecified значениями DateTime, записываемыми в столбцы с часовым поясом, отличным от UTC, включайте часовой пояс в SQL:

    var parameters = new ClickHouseParameterCollection();
    parameters.AddParameter("dt", myDateTime);
    
    await client.ExecuteNonQueryAsync(
        "INSERT INTO table (dt) VALUES ({dt:DateTime('Europe/Amsterdam')})",
        parameters
    );
    

Асинхронные вставки

Асинхронные вставки переносят ответственность за формирование батчей с клиента на сервер. Вместо необходимости группировать вставки на стороне клиента сервер буферизует входящие данные и сбрасывает их в хранилище при достижении настраиваемых пороговых значений. Это полезно в сценариях с высокой степенью параллелизма, например в нагрузках обсервабилити, когда множество агентов отправляют небольшие объемы данных.

Включите асинхронные вставки через CustomSettings или строку подключения:

// Using CustomSettings
var settings = new ClickHouseClientSettings("Host=localhost");
settings.CustomSettings["async_insert"] = 1;
settings.CustomSettings["wait_for_async_insert"] = 1; // Recommended: wait for flush acknowledgment

// Or via connection string
// "Host=localhost;set_async_insert=1;set_wait_for_async_insert=1"

Два режима (управляются параметром wait_for_async_insert):

ModeBehaviorUse case
wait_for_async_insert=1Вставка (INSERT) завершается после сброса данных на диск. Ошибки возвращаются клиенту.Рекомендуется для большинства нагрузок
wait_for_async_insert=0Вставка (INSERT) завершается сразу после буферизации данных. Нет гарантии сохранения данных.Только когда допустима потеря данных
Примечание

При wait_for_async_insert=0 ошибки возникают только во время flush и не могут быть однозначно сопоставлены с исходной вставкой. Клиент также не создает обратного давления, что повышает риск перегрузки сервера.

Ключевые настройки:

SettingDescription
async_insert_max_data_sizeВыполнить flush, когда буфер достигает указанного размера (в байтах)
async_insert_busy_timeout_msВыполнить flush по истечении указанного тайм-аута (в миллисекундах)
async_insert_max_query_numberВыполнить flush после накопления указанного числа запросов

Сессии

Включайте сессии только тогда, когда вам нужны серверные возможности с сохранением состояния, например:

  • Временные таблицы (CREATE TEMPORARY TABLE)
  • Сохранение контекста запроса между несколькими командами
  • Настройки на уровне сессии (SET max_threads = 4)

Когда сессии включены, запросы сериализуются, чтобы предотвратить одновременное использование одной и той же сессии. Это добавляет накладные расходы для нагрузок, которым не требуется состояние сессии.

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

var settings = new ClickHouseClientSettings
{
    Host = "localhost",
    UseSession = true,
    SessionId = "my-session",
};

var dataSource = new ClickHouseDataSource(settings);
await using var connection = await dataSource.OpenConnectionAsync();

await using var cmd1 = connection.CreateCommand("CREATE TEMPORARY TABLE temp_ids (id UInt64)");
await cmd1.ExecuteNonQueryAsync();

await using var cmd2 = connection.CreateCommand("INSERT INTO temp_ids VALUES (1), (2), (3)");
await cmd2.ExecuteNonQueryAsync();

await using var cmd3 = connection.CreateCommand("SELECT * FROM users WHERE id IN (SELECT id FROM temp_ids)");
await using var reader = await cmd3.ExecuteReaderAsync();

Поддерживаемые типы данных

ClickHouse.Driver поддерживает все типы данных ClickHouse. В приведённых ниже таблицах показаны сопоставления между типами ClickHouse и нативными типами .NET при чтении данных из базы данных.

Сопоставление типов: чтение из ClickHouse

Целочисленные типы

Тип в ClickHouseТип в .NET
Int8sbyte
UInt8byte
Int16short
UInt16ushort
Int32int
UInt32uint
Int64long
UInt64ulong
Int128BigInteger
UInt128BigInteger
Int256BigInteger
UInt256BigInteger

Типы с плавающей запятой

Тип ClickHouseТип .NET
Float32float
Float64double
BFloat16float

Типы Decimal

Тип ClickHouseТип .NET
Decimal(P, S)decimal / ClickHouseDecimal
Decimal32(S)decimal / ClickHouseDecimal
Decimal64(S)decimal / ClickHouseDecimal
Decimal128(S)decimal / ClickHouseDecimal
Decimal256(S)decimal / ClickHouseDecimal
Примечание

Преобразование типов Decimal управляется настройкой UseCustomDecimals.


Булев тип

Тип ClickHouseТип .NET
Boolbool

Строковые типы

Тип ClickHouseТип .NET
Stringstring
FixedString(N)string
Примечание

По умолчанию столбцы String и FixedString(N) возвращаются как string. Установите параметр ReadStringsAsByteArrays=true в строке подключения, чтобы считывать их как byte[]. Это полезно при хранении двоичных данных, которые могут не быть корректной последовательностью в кодировке UTF-8.


Типы даты и времени

ClickHouse Type.NET Type
DateDateTime
Date32DateTime
DateTimeDateTime
DateTime32DateTime
DateTime64DateTime
TimeTimeSpan
Time64TimeSpan

ClickHouse хранит значения DateTime и DateTime64 во внутреннем представлении как Unix-временные метки (Unix timestamps — секунды или доли секунды, прошедшие с начала эпохи Unix). Хотя хранение всегда ведётся в UTC, у столбцов может быть привязан часовой пояс, который влияет на то, как значения отображаются и интерпретируются.

При чтении значений DateTime свойство DateTime.Kind устанавливается на основе часового пояса столбца:

Column DefinitionReturned DateTime.KindNotes
DateTime('UTC')UtcЯвный часовой пояс UTC
DateTime('Europe/Amsterdam')UnspecifiedПрименяется часовой пояс со смещением
DateTimeUnspecifiedЛокальное (wall-clock) время сохраняется как есть

Для столбцов с часовым поясом, отличным от UTC, возвращаемое значение DateTime представляет локальное (wall-clock) время в соответствующем часовом поясе. Используйте ClickHouseDataReader.GetDateTimeOffset() для получения DateTimeOffset с корректным смещением для этого часового пояса:

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 вместо DateTime('Europe/Amsterdam')) драйвер возвращает DateTime с Kind=Unspecified. Это позволяет сохранить «настенное» время в точности в том виде, как оно хранится, не делая предположений о часовом поясе.

Если вам требуется поведение с учетом часового пояса для столбцов без явного часового пояса, то:

  1. Используйте явные часовые пояса в определениях столбцов: DateTime('UTC') или DateTime('Europe/Amsterdam')
  2. Устанавливайте нужный часовой пояс самостоятельно после чтения данных.

Тип JSON

ClickHouse Type.NET TypeNotes
JsonJsonObjectПо умолчанию (JsonReadMode=Binary)
JsonstringПри JsonReadMode=String

Тип возвращаемого значения JSON-столбцов определяется параметром JsonReadMode:

  • Binary (по умолчанию): Возвращает System.Text.Json.Nodes.JsonObject. Обеспечивает структурированный доступ к данным JSON, но специализированные типы ClickHouse (такие как IP-адреса, UUID, большие десятичные числа) преобразуются в их строковые представления внутри JSON-структуры.

  • String: Возвращает «сырое» значение JSON как string. Сохраняет точное представление JSON из ClickHouse, что полезно, когда нужно передать JSON дальше без парсинга или когда вы хотите выполнять десериализацию самостоятельно.

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

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

Другие типы

Тип ClickHouseТип .NET
UUIDGuid
IPv4IPAddress
IPv6IPAddress
NothingDBNull
DynamicСм. примечание
Array(T)T[]
Tuple(T1, T2, ...)Tuple<T1, T2, ...> / LargeTuple
Map(K, V)Dictionary<K, V>
Nullable(T)T?
Enum8string
Enum16string
LowCardinality(T)Такой же, как T
SimpleAggregateFunctionТакой же, как базовый тип
Nested(...)Tuple[]
Variant(T1, T2, ...)См. примечание
QBit(T, dimension)T[]
Примечание

Типы Dynamic и Variant будут преобразованы в тип, соответствующий фактическому базовому типу в каждой строке.


Типы геометрии

Тип ClickHouseТип .NET
PointTuple<double, double>
RingTuple<double, double>[]
LineStringTuple<double, double>[]
PolygonRing[]
MultiLineStringLineString[]
MultiPolygonPolygon[]
GeometryСм. примечание
Примечание

Тип Geometry — это тип Variant, который может содержать любой из геометрических типов. Он будет преобразован в соответствующий тип.


Сопоставление типов: запись в ClickHouse

При вставке данных драйвер преобразует типы .NET в соответствующие типы ClickHouse. В таблицах ниже показано, какие типы .NET поддерживаются для каждого типа столбца ClickHouse.

Целочисленные типы

Тип ClickHouseПринимаемые типы .NETПримечания
Int8sbyte, любой, совместимый с Convert.ToSByte()
UInt8byte, любой, совместимый с Convert.ToByte()
Int16short, любой, совместимый с Convert.ToInt16()
UInt16ushort, любой, совместимый с Convert.ToUInt16()
Int32int, любой, совместимый с Convert.ToInt32()
UInt32uint, любой, совместимый с Convert.ToUInt32()
Int64long, любой, совместимый с Convert.ToInt64()
UInt64ulong, любой, совместимый с Convert.ToUInt64()
Int128BigInteger, decimal, double, float, int, uint, long, ulong, любой, совместимый с Convert.ToInt64()
UInt128BigInteger, decimal, double, float, int, uint, long, ulong, любой, совместимый с Convert.ToInt64()
Int256BigInteger, decimal, double, float, int, uint, long, ulong, любой, совместимый с Convert.ToInt64()
UInt256BigInteger, decimal, double, float, int, uint, long, ulong, любой, совместимый с Convert.ToInt64()

Типы с плавающей запятой

Тип ClickHouseПоддерживаемые типы .NETПримечания
Float32float, любой тип, совместимый с Convert.ToSingle()
Float64double, любой тип, совместимый с Convert.ToDouble()
BFloat16float, любой тип, совместимый с Convert.ToSingle()Усекает значение до 16-битного формата brain float

Логический тип

Тип ClickHouseДопустимые типы .NETПримечания
Boolbool

Строковые типы

Тип ClickHouseДопустимые типы .NETПримечания
Stringstring, byte[], ReadOnlyMemory<byte>, StreamДвоичные типы записываются напрямую; потоки могут как поддерживать произвольное позиционирование (seek), так и не поддерживать его
FixedString(N)string, byte[], ReadOnlyMemory<byte>, StreamString кодируется в UTF-8 и дополняется; двоичные типы должны содержать ровно N байт

Типы даты и времени

Тип ClickHouseДопустимые типы .NETПримечания
DateDateTime, DateTimeOffset, DateOnly, типы NodaTimeПреобразуется в количество Unix-дней как UInt16
Date32DateTime, DateTimeOffset, DateOnly, типы NodaTimeПреобразуется в количество Unix-дней как Int32
DateTimeDateTime, DateTimeOffset, DateOnly, типы NodaTimeСм. подробности ниже
DateTime32DateTime, DateTimeOffset, DateOnly, типы NodaTimeТо же, что и DateTime
DateTime64DateTime, DateTimeOffset, DateOnly, типы NodaTimeТочность зависит от параметра Scale
TimeTimeSpan, intОграничивается диапазоном ±999:59:59; значения int интерпретируются как секунды
Time64TimeSpan, decimal, double, float, int, long, stringСтрока разбирается как [-]HHH:MM:SS[.fraction]; ограничивается до ±999:59:59.999999999

Драйвер учитывает DateTime.Kind при записи значений:

DateTime.KindHTTP-параметрыПакетная загрузка
UtcМомент времени сохраняетсяМомент времени сохраняется
LocalМомент времени сохраняетсяМомент времени сохраняется
UnspecifiedРассматривается как локальное «настенное» время в часовом поясе типа параметра (по умолчанию UTC)Рассматривается как локальное «настенное» время в часовом поясе столбца

Значения DateTimeOffset всегда сохраняют точный момент времени.

Пример: DateTime в UTC (момент сохраняется)

var utcTime = new DateTime(2024, 1, 15, 12, 0, 0, DateTimeKind.Utc);
// Stored as 12:00 UTC
// Read from DateTime('Europe/Amsterdam') column: 13:00 (UTC+1)
// Read from DateTime('UTC') column: 12:00 UTC

Пример: неопределённый DateTime (локальное «настенное» время)

var wallClock = new DateTime(2024, 1, 15, 14, 30, 0, DateTimeKind.Unspecified);
// Written to DateTime('Europe/Amsterdam') column: stored as 14:30 Amsterdam time
// Read back from DateTime('Europe/Amsterdam') column: 14:30

Рекомендация: для наиболее простого и предсказуемого поведения используйте DateTimeKind.Utc или DateTimeOffset для всех операций с типом DateTime. Это позволит вашему коду работать одинаково независимо от часового пояса сервера, клиента или часового пояса столбца.

HTTP-параметры vs bulk copy

Существует существенное отличие между привязкой HTTP-параметров и bulk copy при записи значений DateTime с Kind Unspecified:

Bulk Copy знает часовой пояс целевого столбца и корректно интерпретирует значения Unspecified в этом часовом поясе.

HTTP-параметры автоматически не знают часовой пояс столбца. Необходимо явно указать его в подсказке SQL-типа:

// CORRECT: Timezone in SQL type hint - type is extracted automatically
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime('Europe/Amsterdam')})";
command.AddParameter("dt", myDateTime);

// INCORRECT: Without timezone hint, interpreted as UTC
command.CommandText = "INSERT INTO table (dt_amsterdam) VALUES ({dt:DateTime})";
command.AddParameter("dt", myDateTime);
// String value "2024-01-15 14:30:00" interpreted as UTC, not Amsterdam time!
DateTime.KindЦелевой столбецHTTP-параметр (с указанием часового пояса)HTTP-параметр (без указания часового пояса)Массовое копирование
UtcUTCМомент сохраняетсяМомент сохраняетсяМомент сохраняется
UtcEurope/AmsterdamМомент сохраняетсяМомент сохраняетсяМомент сохраняется
LocalЛюбойМомент сохраняетсяМомент сохраняетсяМомент сохраняется
UnspecifiedUTCИнтерпретируется как UTCИнтерпретируется как UTCИнтерпретируется как UTC
UnspecifiedEurope/AmsterdamИнтерпретируется как время АмстердамаИнтерпретируется как UTCИнтерпретируется как временем Амстердама

Типы Decimal

Тип ClickHouseПоддерживаемые типы .NETПримечания
Decimal(P,S)decimal, ClickHouseDecimal, любой тип, совместимый с Convert.ToDecimal()Выбрасывает исключение OverflowException, если превышена точность
Decimal32decimal, ClickHouseDecimal, любой тип, совместимый с Convert.ToDecimal()Максимальная точность 9
Decimal64decimal, ClickHouseDecimal, любой тип, совместимый с Convert.ToDecimal()Максимальная точность 18
Decimal128decimal, ClickHouseDecimal, любой тип, совместимый с Convert.ToDecimal()Максимальная точность 38
Decimal256decimal, ClickHouseDecimal, любой тип, совместимый с Convert.ToDecimal()Максимальная точность 76

Тип JSON

Тип ClickHouseДопустимые типы .NETПримечания
Jsonstring, 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. Данные конвертируются в бинарный JSON-формат ClickHouse на стороне клиента с полной поддержкой подсказок типов. Перед использованием требуется вызвать connection.RegisterJsonSerializationType<T>(). Запись значений string или JsonNode в этом режиме приводит к выбросу ArgumentException.

// Default String mode works with any input
await client.InsertBinaryAsync(
    "my_table",
    new[] { "id", "data" },
    new[] { new object[] { 1u, new { name = "test", value = 42 } } }
);

// Binary mode requires explicit opt-in and type registration
var settings = new ClickHouseClientSettings("Host=localhost")
{
    JsonWriteMode = JsonWriteMode.Binary
};
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<MyPocoType>();
Типизированные JSON-столбцы

Когда у JSON-столбца есть подсказки по типам (например, JSON(id UInt64, price Decimal128(2))), драйвер использует их, чтобы сериализовывать значения с полным сохранением информации о типах. Это сохраняет точность для таких типов, как UInt64, Decimal, UUID и DateTime64, которые в противном случае теряли бы её при сериализации в виде обычного JSON.

Сериализация POCO

Объекты POCO можно записывать в JSON-столбцы двумя способами в зависимости от значения JsonWriteMode:

Строковый режим (по умолчанию): объекты POCO сериализуются через System.Text.Json.JsonSerializer. Регистрация типов не требуется. Это самый простой подход, который работает и с анонимными объектами.

Бинарный режим: объекты POCO сериализуются с использованием бинарного JSON-формата драйвера с полной поддержкой подсказок типов (type hints). Типы должны быть зарегистрированы с помощью connection.RegisterJsonSerializationType<T>() перед использованием. В этом режиме поддерживаются пользовательские отображения путей через атрибуты:

  • [ClickHouseJsonPath("path")]: Отображает свойство на пользовательский JSON-путь. Полезно для вложенных структур или когда имя свойства отличается от требуемого JSON-ключа. Работает только в бинарном режиме.

  • [ClickHouseJsonIgnore]: Исключает свойство из сериализации. Работает только в бинарном режиме.

CREATE TABLE events (
    id UInt32,
    data JSON(`user.id` Int64, `user.name` String, Timestamp DateTime64(3))
) ENGINE = MergeTree() ORDER BY id
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; }  // Not serialized
}

// For Binary mode: Register the type and enable Binary mode
var settings = new ClickHouseClientSettings("Host=localhost") { JsonWriteMode = JsonWriteMode.Binary };
using var client = new ClickHouseClient(settings);
client.RegisterJsonSerializationType<UserEvent>();

// Insert POCO - serialized to JSON with nested structure via custom path attributes
await client.InsertBinaryAsync(
    "events",
    new[] { "id", "data" },
    new[] { new object[] { 1u, new UserEvent { UserId = 123, UserName = "Alice", Timestamp = DateTime.UtcNow } } }
);
// Resulting JSON: {"user": {"id": 123, "name": "Alice"}, "Timestamp": "2024-01-15T..."}

Сопоставление имени свойства с подсказками типа столбца выполняется с учётом регистра. Свойство UserId будет сопоставлено только с подсказкой, определённой как UserId, а не userid. Это соответствует поведению ClickHouse, который позволяет путям вроде userName и UserName существовать как отдельные поля.

Ограничения (только бинарный режим):

  • Типы POCO должны быть зарегистрированы на подключении с помощью connection.RegisterJsonSerializationType<T>() до сериализации. Попытка сериализовать незарегистрированный тип приводит к возникновению ClickHouseJsonSerializationException.
  • Свойства-словари и массивы/списки требуют подсказок типов в определении столбца для корректной сериализации. Без подсказок используйте вместо этого строковый режим (String mode).
  • Значения null в свойствах POCO записываются только тогда, когда путь имеет подсказку типа Nullable(T) в определении столбца. ClickHouse не допускает типы Nullable внутри динамических JSON-путей, поэтому свойства с null без подсказок пропускаются.
  • Атрибуты ClickHouseJsonPath и ClickHouseJsonIgnore игнорируются в строковом режиме (они работают только в бинарном режиме).

Другие типы

Тип ClickHouseПринимаемые типы .NETПримечания
UUIDGuid, stringСтрока парсится как Guid
IPv4IPAddress, stringДолжен быть IPv4; строка парсится через IPAddress.Parse()
IPv6IPAddress, stringДолжен быть IPv6; строка парсится через IPAddress.Parse()
NothingЛюбой типНичего не записывает (операция no-op)
DynamicНе поддерживается (выбрасывает NotImplementedException)
Array(T)IList, nullПри значении null записывается пустой массив
Tuple(T1, T2, ...)ITuple, IListКоличество элементов должно соответствовать арности кортежа
Map(K, V)IDictionary
Nullable(T)null, DBNull или типы, принимаемые TПеред значением записывается байт флага null
Enum8string, sbyte, числовые типыСтроковое значение ищется в словаре enum
Enum16string, short, числовые типыСтроковое значение ищется в словаре enum
LowCardinality(T)Типы, принимаемые TДелегирует базовому типу
SimpleAggregateFunctionТипы, принимаемые базовым типомДелегирует базовому типу
Nested(...)IList кортежейКоличество элементов должно соответствовать количеству полей
Variant(T1, T2, ...)Значение, соответствующее одному из T1, T2, ...Выбрасывает ArgumentException, если нет совпадения типа
QBit(T, dim)IListДелегирует типу Array; размерность — только метаданные

Геометрические типы

Тип ClickHouseДопустимые типы .NETПримечания
PointSystem.Drawing.Point, ITuple, IList (2 элемента)
RingIList из Point
LineStringIList из Point
PolygonIList из Ring
MultiLineStringIList из LineString
MultiPolygonIList из Polygon
GeometryЛюбой из указанных выше геометрических типовОбобщающий вариант всех геометрических типов

Запись не поддерживается

Тип ClickHouseПримечания
DynamicВызывает исключение NotImplementedException
AggregateFunctionВызывает исключение AggregateFunctionException

Обработка вложенных типов

Вложенные типы ClickHouse (Nested(...)) можно читать и записывать с использованием семантики массивов.

CREATE TABLE test.nested (
    id UInt32,
    params Nested (param_id UInt8, param_val String)
) ENGINE = Memory
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 }
);

Журналирование и диагностика

Клиент ClickHouse для .NET интегрируется с абстракциями логирования Microsoft.Extensions.Logging, предоставляя легковесное журналирование, подключаемое по желанию. При его включении драйвер генерирует структурированные сообщения о событиях жизненного цикла подключения, выполнении команд, транспортных операциях и массовых операциях вставки. Журналирование полностью необязательно — приложения, которые не настраивают логгер, продолжают работать без дополнительных накладных расходов.

Быстрый старт

using ClickHouse.Driver;
using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Information);
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);

Использование appsettings.json

Вы можете настроить уровни логирования с помощью стандартной системы конфигурации .NET:

using ClickHouse.Driver;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var configuration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .Build();

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(configuration.GetSection("Logging"))
        .AddConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);

Использование конфигурации в оперативной памяти

Вы также можете настроить детализацию логирования по категориям прямо в коде:

using ClickHouse.Driver;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

var categoriesConfiguration = new Dictionary<string, string>
{
    { "LogLevel:Default", "Warning" },
    { "LogLevel:ClickHouse.Driver.Connection", "Information" },
    { "LogLevel:ClickHouse.Driver.Command", "Debug" }
};

var config = new ConfigurationBuilder()
    .AddInMemoryCollection(categoriesConfiguration)
    .Build();

using var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConfiguration(config)
        .AddSimpleConsole();
});

var settings = new ClickHouseClientSettings("Host=localhost;Port=8123")
{
    LoggerFactory = loggerFactory
};

using var client = new ClickHouseClient(settings);

Категории и источники

Драйвер использует отдельные категории, чтобы вы могли точно настраивать уровни логирования для каждого компонента:

CategorySourceHighlights
ClickHouse.Driver.ConnectionClickHouseConnectionЖизненный цикл соединения, выбор фабрики HTTP‑клиента, открытие/закрытие соединения, управление сессиями.
ClickHouse.Driver.CommandClickHouseCommandНачало и завершение выполнения запроса, замер времени, идентификаторы запросов, статистика сервера и сведения об ошибках.
ClickHouse.Driver.TransportClickHouseConnectionНизкоуровневые потоковые HTTP‑запросы, флаги сжатия, коды статуса ответа и сбои транспортного уровня.
ClickHouse.Driver.ClientClickHouseClientБинарная вставка, запросы и другие операции.
ClickHouse.Driver.NetTraceTraceHelperОтслеживание сетевых операций, только при включённом режиме отладки.

Пример: диагностика неполадок подключения

{
    "Logging": {
        "LogLevel": {
            "ClickHouse.Driver.Connection": "Trace",
            "ClickHouse.Driver.Transport": "Trace"
        }
    }
}

В журнал будет записано:

  • выбор фабрики HTTP-клиента (пул по умолчанию по сравнению с одиночным подключением)
  • конфигурация HTTP-обработчика (SocketsHttpHandler или HttpClientHandler)
  • настройки пула подключений (MaxConnectionsPerServer, PooledConnectionLifetime и т. д.)
  • параметры тайм-аутов (ConnectTimeout, Expect100ContinueTimeout и т. д.)
  • конфигурация SSL/TLS
  • события открытия и закрытия подключений
  • отслеживание идентификаторов сессий

Режим отладки: трассировка сети и диагностика

Чтобы упростить диагностику сетевых проблем, библиотека драйвера предоставляет вспомогательный инструмент, позволяющий включить низкоуровневую трассировку внутренних сетевых механизмов .NET. Чтобы включить её, необходимо передать LoggerFactory с уровнем Trace и установить EnableDebugMode в значение true (или включить её вручную через класс ClickHouse.Driver.Diagnostic.TraceHelper). События будут логироваться в категорию ClickHouse.Driver.NetTrace. Предупреждение: это приведёт к генерации чрезвычайно подробных логов и повлияет на производительность. Не рекомендуется включать режим отладки в продуктивной среде.

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .AddConsole()
        .SetMinimumLevel(LogLevel.Trace); // Must be Trace level to see network events
});

var settings = new ClickHouseClientSettings()
{
    LoggerFactory = loggerFactory,
    EnableDebugMode = true,  // Enable low-level network tracing
};

OpenTelemetry

Драйвер предоставляет встроенную поддержку распределённого трейсинга OpenTelemetry через API .NET System.Diagnostics.Activity. При его включении драйвер генерирует спаны для операций с базой данных, которые могут быть экспортированы в обсервабилити-бэкенды, такие как Jaeger или сам ClickHouse (через OpenTelemetry Collector).

Включение трассировки

В приложениях ASP.NET Core добавьте ActivitySource драйвера ClickHouse в конфигурацию OpenTelemetry:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource(ClickHouseDiagnosticsOptions.ActivitySourceName)  // Subscribe to ClickHouse driver spans
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());             // Or AddJaegerExporter(), etc.

Для консольных приложений, тестирования или ручной настройки:

using OpenTelemetry;
using OpenTelemetry.Trace;

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

Атрибуты спана

Каждый спан включает стандартные атрибуты базы данных OpenTelemetry, а также специфичные для ClickHouse статистические данные по запросу, которые можно использовать для отладки.

АтрибутОписание
db.systemВсегда "clickhouse"
db.nameИмя базы данных
db.userИмя пользователя
db.statementSQL-запрос (если включено)
db.clickhouse.read_rowsКоличество строк, прочитанных запросом
db.clickhouse.read_bytesКоличество байт, прочитанных запросом
db.clickhouse.written_rowsКоличество строк, записанных запросом
db.clickhouse.written_bytesКоличество байт, записанных запросом
db.clickhouse.elapsed_nsВремя выполнения на стороне сервера в наносекундах

Параметры конфигурации

Настройте поведение трассировки с помощью ClickHouseDiagnosticsOptions:

using ClickHouse.Driver.Diagnostic;

// Include SQL statements in spans (default: false for security)
ClickHouseDiagnosticsOptions.IncludeSqlInActivityTags = true;

// Truncate long SQL statements (default: 1000 characters)
ClickHouseDiagnosticsOptions.StatementMaxLength = 500;
Примечание

Включение IncludeSqlInActivityTags может привести к раскрытию конфиденциальных данных в ваших трассировках. Используйте с осторожностью в производственных средах.

Конфигурация TLS

При подключении к ClickHouse по HTTPS вы можете по‑разному настроить работу TLS/SSL.

Пользовательская проверка сертификатов

Для продакшн-сред, где требуется собственная логика проверки сертификатов, используйте свой HttpClient с настроенным обработчиком ServerCertificateCustomValidationCallback:

using System.Net;
using System.Net.Security;
using ClickHouse.Driver;

var handler = new HttpClientHandler
{
    // Required when compression is enabled (default)
    AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,

    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);
Примечание

Важные замечания при передаче собственного HttpClient

  • Автоматическая декомпрессия: необходимо включить AutomaticDecompression, если сжатие не отключено (по умолчанию сжатие включено).
  • Тайм-аут простоя: установите PooledConnectionIdleTimeout меньше, чем keep_alive_timeout сервера (10 секунд для ClickHouse Cloud), чтобы избежать ошибок подключения из‑за полуоткрытых соединений.

Поддержка ORM

ORM-фреймворки используют API ADO.NET (ClickHouseConnection). Для корректного управления жизненным циклом подключений создавайте их из ClickHouseDataSource:

// Register DataSource as singleton
var dataSource = new ClickHouseDataSource("Host=localhost;Username=default");

// Create connections for ORM use
await using var connection = await dataSource.OpenConnectionAsync();
// Pass connection to your ORM...

Dapper

ClickHouse.Driver можно использовать с Dapper, но анонимные объекты при этом не поддерживаются.

Рабочий пример:

connection.QueryAsync<string>(
    "SELECT {p1:Int32}",
    new Dictionary<string, object> { { "p1", 42 } }
);

Не поддерживается:

connection.QueryAsync<string>(
    "SELECT {p1:Int32}",
    new { p1 = 42 }
);

Linq2db

Этот драйвер совместим с linq2db — легковесным ORM и провайдером LINQ для .NET. Подробную документацию см. на сайте проекта.

Пример использования:

Создайте объект DataConnection с использованием провайдера ClickHouse:

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‑конфигурации. Если имена ваших классов и свойств в точности совпадают с именами таблиц и столбцов, никакая конфигурация не требуется:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Выполнение запросов:

await using var db = new DataConnection(options);

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

Массовое копирование (Bulk Copy):

Используйте BulkCopyAsync для эффективной массовой вставки данных.

await using var db = new DataConnection(options);
var table = db.GetTable<Product>();

var options = new BulkCopyOptions
{
    MaxBatchSize = 100000,
    MaxDegreeOfParallelism = 1,
    WithoutSession = true
};

await table.BulkCopyAsync(options, products);

Entity framework core

Entity Framework Core на данный момент не поддерживается.

Ограничения

Столбцы типа AggregateFunction

Столбцы типа AggregateFunction(...) нельзя напрямую использовать в запросах или при вставке данных.

Для вставки:

INSERT INTO t VALUES (uniqState(1));

Чтобы выбрать:

SELECT uniqMerge(c) FROM t;