Maurosoft.RepoDb.ClickHouse
1.0.13
dotnet add package Maurosoft.RepoDb.ClickHouse --version 1.0.13
NuGet\Install-Package Maurosoft.RepoDb.ClickHouse -Version 1.0.13
<PackageReference Include="Maurosoft.RepoDb.ClickHouse" Version="1.0.13" />
<PackageVersion Include="Maurosoft.RepoDb.ClickHouse" Version="1.0.13" />
<PackageReference Include="Maurosoft.RepoDb.ClickHouse" />
paket add Maurosoft.RepoDb.ClickHouse --version 1.0.13
#r "nuget: Maurosoft.RepoDb.ClickHouse, 1.0.13"
#:package Maurosoft.RepoDb.ClickHouse@1.0.13
#addin nuget:?package=Maurosoft.RepoDb.ClickHouse&version=1.0.13
#tool nuget:?package=Maurosoft.RepoDb.ClickHouse&version=1.0.13
Maurosoft.RepoDb.ClickHouse
A RepoDb extension that adds first-class ClickHouse support, built on top of the official ADO.NET provider, ClickHouse.Driver.
It wires up RepoDb's IDbSetting, IDbHelper, and IStatementBuilder for ClickHouse's SQL dialect, adapts RepoDb's ADO.NET parameter conventions to ClickHouse.Driver's native substitution syntax, and adds a native RowBinary bulk-insert path for high-throughput ingestion — so you get RepoDb's familiar, low-ceremony API (InsertAsync, QueryAsync, MergeAsync, …) working correctly against a database engine that was never designed around classic OLTP semantics.
Features
- Full RepoDb CRUD support (
InsertAsync,QueryAsync,UpdateAsync,DeleteAsync,MergeAsync,CountAsync,ExistsAsync, …) targeting ClickHouse - Correct SQL generation for ClickHouse's dialect: backtick-quoted identifiers,
LIMIT/OFFSETpagination,ALTER TABLE ... UPDATE/DELETEmutations,INSERT-based upsert fallback forMERGE - Transparent ADO.NET parameter adaptation — write ordinary RepoDb expressions; the library rewrites parameters into ClickHouse.Driver's native
{name:Type}substitution syntax under the hood - Native RowBinary bulk insert (
BulkInsertAsync) for batches in the hundreds of thousands of rows, with streaming (IAsyncEnumerable<T>) support - Schema introspection via
system.columns, with ClickHouse-to-.NET type mapping (DateTime64,LowCardinality,UUID,Array(T),Nullable(T), …) - A typed
ClickHouseRepository<TEntity>base class for a clean-architecture-friendly repository pattern
Installation
dotnet add package Maurosoft.RepoDb.ClickHouse
or via PackageReference:
<PackageReference Include="Maurosoft.RepoDb.ClickHouse" Version="1.1.0" />
Requirements
| Target framework | .NET 9.0 |
RepoDb |
1.15.1 or later |
ClickHouse.Driver |
1.3.0 or later |
| ClickHouse server | 22.8+ (lightweight DELETE support required) |
Both dependencies are pulled in automatically by the NuGet package — no extra PackageReference entries needed for them.
Getting a ClickHouse server (Docker)
The fastest way to get a ClickHouse instance for local development is Docker:
docker run -d --name clickhouse \
-p 8123:8123 -p 9000:9000 \
-e CLICKHOUSE_DB=mydb \
-e CLICKHOUSE_USER=default \
-e CLICKHOUSE_PASSWORD=changeme \
clickhouse/clickhouse-server:24.10-alpine
Or with docker-compose.yml:
services:
clickhouse:
image: clickhouse/clickhouse-server:24.10-alpine
ports:
- "8123:8123" # HTTP interface, used by ClickHouse.Driver
- "9000:9000" # native TCP interface
environment:
CLICKHOUSE_DB: mydb
CLICKHOUSE_USER: default
CLICKHOUSE_PASSWORD: changeme
volumes:
- clickhouse-data:/var/lib/clickhouse
volumes:
clickhouse-data:
docker compose up -d
The corresponding connection string for the examples below:
const string ConnectionString =
"Host=localhost;Port=8123;Database=mydb;Username=default;Password=changeme;Protocol=http";
If your test or CI environment can spin up containers on demand, Testcontainers for .NET (specifically the Testcontainers.ClickHouse module) is a good fit for integration tests — this is exactly what this library's own test suite uses.
Setup
Register ClickHouse support with RepoDb once, at application startup:
using RepoDb;
using RepoDb.ClickHouse;
// Program.cs — once, before any RepoDb call
GlobalConfiguration.Setup().UseClickHouse();
Quick start
Always use RepoDbClickHouseConnection — not ClickHouse.Driver.ADO.ClickHouseConnection
directly — when calling RepoDb extension methods (InsertAsync, QueryAsync, UpdateAsync, …).
RepoDb resolves the registered IDbSetting/IDbHelper/IStatementBuilder by the compile-time
type of the connection variable, and ClickHouse.Driver 1.3+ no longer understands the @name
ADO.NET parameter placeholders RepoDb generates — it requires its own {name:Type} substitution
syntax. RepoDbClickHouseConnection rewrites every command automatically before execution; the raw
driver connection does not. This is not required for BulkInsertAsync, which uses the RowBinary
binary protocol instead of parameterized SQL text.
using RepoDb;
using RepoDb.ClickHouse;
using RepoDb.Attributes;
[Map("events")]
public class Event
{
[Primary]
public Guid Id { get; set; }
public string Status { get; set; } = "";
public DateTime CreatedAt { get; set; }
}
await using var conn = new RepoDbClickHouseConnection(ConnectionString);
// Insert
await conn.InsertAsync(new Event { Id = Guid.NewGuid(), Status = "active", CreatedAt = DateTime.UtcNow });
// Query with a LINQ-style expression
var active = await conn.QueryAsync<Event>(e => e.Status == "active");
// Batched insert (parameterized ADO.NET path, auto-rewritten for ClickHouse)
await conn.InsertAllAsync(events, batchSize: 5_000);
// High-throughput native bulk insert (RowBinary protocol)
await conn.BulkInsertAsync(events, batchSize: 100_000);
// Raw SQL for ClickHouse-specific query features
var result = await conn.ExecuteQueryAsync<Event>(
"SELECT * FROM events WHERE toDate(CreatedAt) = today()");
Bulk insert with tuning options
BulkInsertAsync uses ClickHouse's native RowBinary protocol and is the recommended path once you are
inserting more than roughly 100,000 rows at a time:
using RepoDb.ClickHouse.Bulk;
var result = await conn.BulkInsertAsync(events, ClickHouseBulkInsertOptions.HighThroughput with
{
BatchSize = 250_000
});
Console.WriteLine(result);
// BulkInsert 'events': 1,000,000 rows in 4 batches, 6.12s (163,398 rows/s)
It also accepts an IAsyncEnumerable<T> for streaming ingestion from a file, queue, or upstream query
without buffering the whole source in memory:
await conn.BulkInsertAsync(ReadEventsFromKafkaAsync(), ClickHouseBulkInsertOptions.Default);
Repository pattern
ClickHouseRepository<TEntity> gives you a typed base class for a clean-architecture-friendly
repository, on top of the same RepoDbClickHouseConnection-backed pipeline:
public interface IEventRepository
{
Task<IEnumerable<Event>> GetByStatusAsync(string status);
Task UpsertAsync(Event ev);
}
public class EventRepository : ClickHouseRepository<Event>, IEventRepository
{
public EventRepository(string connectionString) : base(connectionString) { }
public Task<IEnumerable<Event>> GetByStatusAsync(string status)
=> QueryAsync(e => e.Status == status);
public Task UpsertAsync(Event ev)
=> MergeAsync(ev); // translates to an INSERT against a ReplacingMergeTree table
}
Architecture
| RepoDb contract | Implementation | Responsibility |
|---|---|---|
IDbConnection |
RepoDbClickHouseConnection |
Rewrites @name → {name:Type} before every execution; returns a no-op transaction |
IDbSetting |
ClickHouseDbSetting |
Backtick quoting, @ parameter prefix, no SQL-standard schema concept |
IDbHelper |
ClickHouseDbHelper |
Schema introspection via system.columns |
IStatementBuilder |
ClickHouseStatementBuilder |
Generates SQL for ClickHouse's dialect |
| Repository base | ClickHouseRepository<T> |
Extends BaseRepository<T, RepoDbClickHouseConnection> |
| Bulk engine | ClickHouseBulkInserter<T> |
Native RowBinary insert via ClickHouseClient.InsertBinaryAsync |
ClickHouse-specific behavior
UPDATE (mutations)
ClickHouse has no classic UPDATE statement. The library generates a mutation instead:
ALTER TABLE `table` UPDATE col = @col WHERE ...
Mutations run asynchronously on the server — the update is not guaranteed to be visible to reads
that immediately follow it. For upsert-style workloads, prefer ReplacingMergeTree combined with
MergeAsync (which issues an INSERT) instead of updating rows in place.
DELETE
Uses ClickHouse's lightweight delete (available since 22.8):
DELETE FROM `table` WHERE ...
On MergeTree-family tables, matching rows are marked for deletion and physically removed on the next
background merge.
MERGE / upsert
ClickHouse has no MERGE statement. MergeAsync falls back to a plain INSERT, which only produces
correct upsert semantics against:
ReplacingMergeTree— deduplicated on merge, keeping the most recently inserted row per sort keyCollapsingMergeTree— logical deletion via sign-column rows
Using it against a plain MergeTree table simply inserts a duplicate row.
Transactions
ClickHouse has no ACID transactions — every statement is auto-committed by the engine.
RepoDbClickHouseConnection.BeginTransaction() returns a no-op transaction instead of throwing:
Commit()/Rollback() are silently ignored. This is required because RepoDb always opens an implicit
transaction around its batch operations (InsertAllAsync, DeleteAllAsync, UpdateAllAsync, …).
Type mapping
The schema resolver maps native ClickHouse types to their .NET equivalents, including
DateTime/DateTime64, Date/Date32 → DateOnly, UUID → Guid, and unwraps
Nullable(T)/LowCardinality(T) wrappers transparently.
Performance tips
- Prefer
BulkInsertAsyncoverInsertAllAsynconce batches exceed roughly 100,000 rows — the native RowBinary protocol has significantly less overhead than parameterized SQL text. - When using
InsertAllAsync, usebatchSize >= 1,000. - Filter or order on primary-key/sort-key columns wherever possible — ClickHouse's
MergeTreeengines are built around efficient range scans on the sort key. - For complex OLAP queries (aggregations,
ARRAY JOIN, window functions), preferExecuteQueryAsyncwith raw SQL over composing an equivalent LINQ expression. - Avoid unfiltered
QueryAllAsyncon large tables — always add aWHEREclause or aLIMIT.
Continuous integration
Every push to main or develop runs .github/workflows/ci.yml:
| Job | Runs on | Does |
|---|---|---|
| Build | main, develop |
Restores and builds the solution in Release, computing the package version from version.json via Nerdbank.GitVersioning. |
| Unit Tests + Integration Tests (Testcontainers) | main, develop |
Runs the full test suite — including the Testcontainers.ClickHouse-backed integration tests, which spin up a real ClickHouse container per run — then publishes coverage to Codecov, to the workflow run summary, and (on main only) back into this README. |
| Pack NuGet | main, develop |
Packs the library into a .nupkg, uploaded as a downloadable workflow artifact. |
| Publish to NuGet.org | main only |
Pushes the package built above to nuget.org. |
| GitHub Release | main only |
Tags the commit and publishes a GitHub Release with the .nupkg attached and auto-generated release notes. |
Pushes to develop therefore stop after Pack NuGet — you get a downloadable prerelease artifact for
every commit without publishing anything permanent to nuget.org or cutting a release. Only main
completes the last two jobs.
Publishing to NuGet.org uses Trusted Publishing
(OIDC), not a long-lived API key. The Publish to NuGet.org job exchanges a short-lived GitHub OIDC
token for a temporary (1-hour) nuget.org API key at push time via NuGet/login,
so there is no static secret to leak, rotate, or revoke.
To fork or self-host this pipeline:
- On nuget.org → your profile → Trusted Publishing, add a policy pointing at
your fork: repository owner, repository name, and workflow file
ci.yml(filename only, not the.github/workflows/path). - Configure two repository secrets:
| Secret | Used by | Purpose |
|---|---|---|
NUGET_USER |
Publish to NuGet.org | Your nuget.org profile name (not your email) — passed to NuGet/login to identify which trusted publishing policy applies. |
CODECOV_TOKEN |
Unit Tests + Integration Tests | Upload token from codecov.io for this repository (recommended even for public repos, to avoid rate limiting). |
Per-class coverage detail
Beyond the aggregate percentage shown by the Codecov badge above, the table below is regenerated on
every push to main by ReportGenerator and committed
straight back into this file — see generate-readme.ps1 and the Regenerate
README step in ci.yml. It reflects coverage as of the last main build,
not necessarily the latest commit on develop.
This table is generated — edit README.tpl, not this section, and let CI regenerate it.
Summary
| Generated on: | 08/07/2026 - 21:07:04 |
| Parser: | Cobertura |
| Assemblies: | 1 |
| Classes: | 20 |
| Files: | 14 |
| Line coverage: | 90.8% (557 of 613) |
| Covered lines: | 557 |
| Uncovered lines: | 56 |
| Coverable lines: | 613 |
| Total lines: | 2055 |
| Branch coverage: | 78.7% (315 of 400) |
| Covered branches: | 315 |
| Total branches: | 400 |
| Method coverage: | Feature is only available for sponsors |
Coverage
| Name | Line | Branch |
|---|---|---|
| Maurosoft.RepoDb.ClickHouse | 90.8% | 78.7% |
| RepoDb.ClickHouse.Bulk.ClickHouseBulkInserter<TEntity> | 96.8% | 85.7% |
| RepoDb.ClickHouse.Bulk.ClickHouseBulkInserter<TEntity> | 100% | 70% |
| RepoDb.ClickHouse.Bulk.ClickHouseBulkInsertOptions | 100% | |
| RepoDb.ClickHouse.Bulk.ClickHouseBulkInsertResult | 100% | 100% |
| RepoDb.ClickHouse.Bulk.EntityFieldMapper.FieldBinding<TEntity> | 100% | |
| RepoDb.ClickHouse.Bulk.EntityFieldMapper<TEntity> | 100% | |
| RepoDb.ClickHouse.Bulk.EntityFieldMapper<TEntity> | 87.8% | 81.8% |
| RepoDb.ClickHouse.Bulk<T> | 96% | 82.8% |
| RepoDb.ClickHouse.ClickHouseBootstrap | 92.3% | 75% |
| RepoDb.ClickHouse.ClickHouseDbRepository | 0% | |
| RepoDb.ClickHouse.ClickHouseParameterizedCommand | 78.5% | 95% |
| RepoDb.ClickHouse.ClickHouseRepository<TEntity> | 0% | |
| RepoDb.ClickHouse.DbHelpers.ClickHouseDbHelper | 91.1% | 79.1% |
| RepoDb.ClickHouse.DbHelpers.ClickHouseDbTypeNameToClientTypeResolver | 100% | 72.7% |
| RepoDb.ClickHouse.DbSettings.ClickHouseDbSetting | 100% | |
| RepoDb.ClickHouse.Extensions.ClickHouseBulkInsertExtensions | 64.7% | 100% |
| RepoDb.ClickHouse.GlobalConfigurationExtension | 100% | |
| RepoDb.ClickHouse.NoOpDbTransaction | 71.4% | |
| RepoDb.ClickHouse.RepoDbClickHouseConnection | 75% | |
| RepoDb.ClickHouse.StatementBuilders.ClickHouseStatementBuilder | 89.5% | 75% |
License
MIT © Mauro Cardillo
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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 was computed. 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. |
-
net9.0
- ClickHouse.Driver (>= 1.3.0)
- RepoDb (>= 1.15.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Updated for RepoDb 1.15.1 and ClickHouse.Driver 1.3.0 compatibility:
- Added RepoDbClickHouseConnection, which rewrites RepoDb's ADO.NET "@name" parameters into ClickHouse.Driver's native "{name:Type}" substitution syntax before every execution (ClickHouse.Driver 1.3+ no longer understands "@name" at all).
- Added a no-op transaction (BeginTransaction) so RepoDb's implicit transactions around batch operations (InsertAllAsync, DeleteAllAsync, UpdateAllAsync) no longer fail against ClickHouse, which has no ACID transactions.
- Rewrote ClickHouseStatementBuilder for RepoDb 1.15.1's IStatementBuilder contract: correct LIMIT/OFFSET pagination, ALTER TABLE mutations for UPDATE/DELETE, INSERT-based MERGE/MERGE ALL fallback, and a single multi-VALUES INSERT ALL statement.
- Fixed schema introspection (system.columns) for ClickHouse.Driver 1.3's native parameter syntax and UInt8-to-byte column mapping.
- Added native RowBinary bulk insert (BulkInsertAsync) with streaming (IAsyncEnumerable) support, tunable batching/parallelism, and diagnostics.
- Removed ClickHouseConnectionExtensions: its convenience wrappers recursed into themselves and crashed with a StackOverflowException; RepoDb's own IDbConnection extensions are used directly instead.
- Full XML documentation across the public and internal API surface.
- Added a GitHub Actions CI/CD pipeline (build, unit + Testcontainers integration tests, NuGet pack/publish, GitHub Release) with Codecov coverage reporting.