ClickHouse C# client

#### ClickHouse-native parameter syntax[​](#dapper-clickhouse-param-syntax "Direct link to ClickHouse-native parameter syntax")

When you need explicit type control, use ClickHouse's `{param:Type}` syntax directly in the SQL with a `Dictionary<string, object>` for the parameter values. Don't combine `@param` syntax and `{param:Type}` syntax for the same parameter.

```
var parameters = new Dictionary<string, object> { { "value", 42 } };
var result = await connection.QueryAsync<int>("SELECT {value:Int32}", parameters);
```

#### WHERE IN[​](#dapper-where-in "Direct link to WHERE IN")

**Dapper's native IN expansion works:**

```
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE id IN @Ids ORDER BY id",
    new { Ids = new[] { 1, 3, 5 } });
```

Dapper rewrites this to `WHERE id IN (@Ids1, @Ids2, @Ids3)`, and the driver converts each expanded parameter.

**ClickHouse's `has()` with Array parameter also works:**

```
var parameters = new Dictionary<string, object> { { "ids", new[] { 1, 3, 5 } } };
var rows = await connection.QueryAsync<User>(
    "SELECT id, name FROM users WHERE has({ids:Array(Int32)}, id) ORDER BY id",
    parameters);
```

#### Custom type handlers[​](#dapper-type-handlers "Direct link to Custom type handlers")

Some ClickHouse types, eg `ITuple`, `BigInteger`, and `ClickHouseDecimal` need handlers registered at startup:

```
// ClickHouseDecimal (for Decimal64/128/256 columns)
SqlMapper.AddTypeHandler(new ClickHouseDecimalHandler());

// BigInteger (for Int128/Int256/UInt128/UInt256 columns)
SqlMapper.AddTypeHandler(new BigIntegerHandler());

// IPAddress (for IPv4/IPv6 columns)
SqlMapper.AddTypeHandler(new IpAddressHandler());
```

See the [Dapper example](https://github.com/ClickHouse/clickhouse-cs/blob/main/examples/ORM/ORM_001_Dapper.cs) for an example type handler implementation.

#### Dapper.Contrib[​](#dapper-contrib "Direct link to Dapper.Contrib")

`GetAll<T>()` and `Get<T>(id)` work. `Insert<T>()` does not — it generates SQL Server syntax (`SCOPE_IDENTITY`, `[]`). It is recommended to use the `ClickHouseClient` native `InsertBinaryAsync` method instead.

```
[Table("test.users")]
record class UserRecord(int Id, string Name, DateTime Timestamp);

var all = await connection.GetAllAsync<UserRecord>();
var one = await connection.GetAsync<UserRecord>(1);
```

Property names must match ClickHouse column names exactly (case-sensitive).

#### Limitations[​](#dapper-limitations "Direct link to Limitations")

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| What Status Details|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Tuple as **result** Works Requires `SqlMapper.TypeHandler<ITuple>` registration| Tuple as **parameter** Not supported Dapper cannot serialize `ITuple`/`Tuple<>` as a `DbParameter` value| Nested types as parameter Not supported Same reason — Dapper rejects complex types as parameter values|  |  |  |  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Geo types as parameter Not supported Point, Ring, Polygon, LineString, MultiLineString, MultiPolygon|  |  |  |  |  |  | | --- | --- | --- | --- | --- | --- | | `Dapper.Contrib.Insert<T>()` Not supported Generates SQL Server-specific syntax|  |  |  | | --- | --- | --- | | `Nothing` type Not supported No meaningful .NET representation | | | | | | | | | | | | | | | | | | | | |

### Linq2db[​](#orm-support-linq2db "Direct link to Linq2db")

This driver is compatible with [linq2db](https://github.com/linq2db/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:

```
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);
```

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:

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

**Querying:**

```
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:**

Use `BulkCopyAsync` for efficient bulk inserts.

```
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[​](#orm-support-ef-core "Direct link to Entity Framework Core")

The official Entity Framework Core provider for ClickHouse. Map C# classes to ClickHouse tables, query with LINQ, and insert data via `SaveChanges` — all using familiar EF Core patterns.

- **NuGet**: [`ClickHouse.EntityFrameworkCore`](https://www.nuget.org/packages/ClickHouse.EntityFrameworkCore)
- **Source**: [GitHub](https://github.com/ClickHouse/ClickHouse.EntityFrameworkCore)

Note

This provider is in early development. It supports **read-only queries** and **inserts**. UPDATE, DELETE, migrations, JOINs, and subqueries are not yet implemented.

#### Installation[​](#ef-core-installation "Direct link to Installation")

```
dotnet add package ClickHouse.EntityFrameworkCore
```

Requires .NET 10.0 and EF Core 10.

#### Quick start[​](#ef-core-quick-start "Direct link to Quick start")

Define your entity and `DbContext`, then query with LINQ:

```
using Microsoft.EntityFrameworkCore;

public class PageView
{
    public long Id { get; set; }
    public string Path { get; set; }
    public DateOnly Date { get; set; }
    public string UserAgent { get; set; }
}

public class AnalyticsContext : DbContext
{
    public DbSet<PageView> PageViews { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseClickHouse("Host=localhost;Database=analytics");
}

// Query
await using var ctx = new AnalyticsContext();

var topPages = await ctx.PageViews
    .Where(v => v.Date >= new DateOnly(2024, 1, 1))
    .GroupBy(v => v.Path)
    .Select(g => new { Path = g.Key, Views = g.Count() })
    .OrderByDescending(x => x.Views)
    .Take(10)
    .ToListAsync();
```

#### Supported types[​](#ef-core-types "Direct link to Supported types")