Get Started for ClickHouse
RepoDB is a hybrid .NET ORM library for ClickHouse, an open-source, columnar OLAP database. The project is hosted at Github and is licensed with Apache 2.0.
Support ships as two packages:
- RepoDb.ClickHouse — the core provider, built on ClickHouse.Driver.
- RepoDb.ClickHouse.BulkOperations — adds
BulkInsert,BulkMerge,BulkUpdate,BulkDeleteandBulkDeleteByKey.
ClickHouse is an OLAP database, not a traditional transactional RDBMS. A few things behave differently than SQL Server/MySQL/PostgreSQL:
- There is no identity/auto-increment/sequence of any kind — key values must always be assigned by the caller.
- There is no native
MERGE/upsert statement — Merge compiles to a plainINSERT, relying on the table engine (e.g.ReplacingMergeTree) for deduplication.UPDATE/DELETEare expressed asALTER TABLE ... UPDATE/DELETEmutations — asynchronous, background operations rather than immediate row-level changes, and theWHEREclause is mandatory.- Transactions are accepted for API compatibility but are not ACID —
Commit()/Rollback()are no-ops.See Operations (ClickHouse) and ClickHouseStatementBuilder for the full detail behind each of these.
Installation
Install the library via NuGet using the Package Manager Console.
> Install-Package RepoDb.ClickHouse
After installation, call the globalized setup method to initialize all dependencies for ClickHouse.
GlobalConfiguration
.Setup()
.UseClickHouse();
To use bulk operations, install the RepoDb.ClickHouse.BulkOperations package.
> Install-Package RepoDb.ClickHouse.BulkOperations
Both packages target
net8.0,net9.0andnet10.0, and depend on ClickHouse.Driver v1.3.0.
Connection String
Host=127.0.0.1;Port=8123;Username=default;Password=YourPassword;Database=RepoDb;Protocol=http;UseCustomDecimals=false;
Always include
UseCustomDecimals=false. Without it,Decimalcolumns come back as the driver’s ownClickHouseDecimaltype instead of a plain .NETdecimal, which RepoDB’s compiled reader cannot cast.
Create a Table
The examples below assume the following table exists in the database.
CREATE TABLE IF NOT EXISTS Person
(
Id UInt64,
Name String,
Age Int32,
CreatedDateUtc DateTime
)
ENGINE = MergeTree
ORDER BY Id;
Create a Model
The examples below assume the following model exists in the application.
public class Person
{
public ulong Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public DateTime CreatedDateUtc { get; set; }
}
Use
RepoDb.ClickHouseConnection(namespaceRepoDb) throughout your application — notClickHouse.Driver.ADO.ClickHouseConnectiondirectly. It’s a thin subclass that lets RepoDB’s provider mappings (DbSetting,DbHelper,StatementBuilder) resolve correctly.
Creating a Record
To insert a row, use the Insert method.
var person = new Person
{
Id = 1,
Name = "John Doe",
Age = 54,
CreatedDateUtc = DateTime.UtcNow
};
using (var connection = new ClickHouseConnection(ConnectionString))
{
connection.Insert(person);
}
To insert multiple rows, use the InsertAll operation.
var people = GetPeople(100);
using (var connection = new ClickHouseConnection(ConnectionString))
{
var rowsInserted = connection.InsertAll(people);
}
ClickHouse has no identity/auto-increment mechanism, so
Idmust always be assigned by the caller before calling Insert/InsertAll. Mapping a property as Identity causes both methods to throw aNotSupportedException, and ClickHouseDbHelper’sGetScopeIdentityalways throws for the same reason.
Querying a Record
To query a row, use the Query method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var person = connection.Query<Person>(e => e.Id == 1);
/* Process the result here */
}
To query all rows, use the QueryAll method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var people = connection.QueryAll<Person>();
/* Process the results here */
}
Merging a Record
To merge a row, use the Merge method.
var person = new Person
{
Id = 1,
Name = "John Doe",
Age = 57,
CreatedDateUtc = DateTime.UtcNow
};
using (var connection = new ClickHouseConnection(ConnectionString))
{
connection.Merge(person);
}
ClickHouse has no native
MERGE/upsert statement. Merge/MergeAll compile to a plainINSERT, identical to Insert/InsertAll — deduplication is deferred to the target table’s engine (e.g.ReplacingMergeTree) and its background merges, which is the idiomatic ClickHouse upsert pattern. For a real, immediate matched/unmatched merge (anUPDATEfor matched rows plus an anti-joinINSERTfor unmatched ones), use BulkMerge from RepoDb.ClickHouse.BulkOperations instead.
To merge multiple rows, use the MergeAll method.
var people = GetPeople(100);
people
.AsList()
.ForEach(p => p.Name = $"{p.Name} (Merged)");
using (var connection = new ClickHouseConnection(ConnectionString))
{
var affectedRecords = connection.MergeAll<Person>(people);
}
Deleting a Record
To delete a row, use the Delete method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var deletedRows = connection.Delete<Person>(1);
}
Other columns can also be used as qualifiers.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var deletedRows = connection.Delete<Person>(p => p.Name == "John Doe");
}
To delete all rows, use the DeleteAll method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var deletedRows = connection.DeleteAll<Person>();
}
Delete/DeleteAll compile to
ALTER TABLE ... DELETE WHERE .... This is an asynchronous mutation in ClickHouse — applied later by background merges, not necessarily before the call returns — and the number of rows reported as affected is not reliable for this statement shape.
Updating a Record
To update a row, use the Update method.
var person = new Person
{
Id = 1,
Name = "James Doe",
Age = 55,
CreatedDateUtc = DateTime.UtcNow
};
using (var connection = new ClickHouseConnection(ConnectionString))
{
var updatedRows = connection.Update<Person>(person);
}
To update multiple rows, use the UpdateAll method.
var people = GetPeople(100);
people
.AsList()
.ForEach(p => p.Name = $"{p.Name} (Updated)");
using (var connection = new ClickHouseConnection(ConnectionString))
{
var updatedRows = connection.UpdateAll<Person>(people);
}
Update/UpdateAll compile to
ALTER TABLE ... UPDATE ... WHERE ..., with the same asynchronous-mutation caveat asDelete. TheWHEREclause is mandatory — ClickHouse rejects an unconditional mutation.
Executing a Query
To execute a non-query statement, use the ExecuteNonQuery method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var sql = "ALTER TABLE Person DELETE WHERE Id = @Id;";
var affectedRecords = connection.ExecuteNonQuery(sql, new { Id = 1 });
}
To execute a query and return mapped objects, use the ExecuteQuery method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var sql = "SELECT * FROM Person ORDER BY Id ASC;";
var people = connection.ExecuteQuery<Person>(sql);
/* Process the results here */
}
To execute a query and return a scalar value, use the ExecuteScalar method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var sql = "SELECT MAX(Id) FROM Person;";
var maxId = connection.ExecuteScalar<ulong>(sql);
}
To execute a query and return a DbDataReader, use the ExecuteReader method.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var sql = "SELECT * FROM Person ORDER BY Id ASC;";
using (var reader = connection.ExecuteReader(sql))
{
/* Process the data reader here */
}
}
Typed Result Execution
Single-column result sets can be mapped to any .NET CLR type via ExecuteQuery.
using (var connection = new ClickHouseConnection(ConnectionString))
{
var sql = "SELECT Name FROM Person WHERE Id = @Id;";
var name = connection.ExecuteQuery<string>(sql, new { Id = 1 });
}
The resultset of this operation is an IEnumerable object.