Preview Support for DuckDB is Now Available in RepoDB
We are excited to announce the preview release of DuckDB support for RepoDB. It brings our hybrid ORM’s fluent CRUD, batch, raw-SQL and Bulk Operations to DuckDB.
Why DuckDB?
DuckDB is an embedded, in-process analytical database, often described as “SQLite for analytics”. It runs inside your application with no server to install. It uses a columnar engine that handles large aggregations, joins and scans quickly. More .NET teams now use it for local analytics, data pipelines, ETL staging and reporting.
Many of these teams are still working with the raw ADO.NET API: hand-written SQL, manual parameter binding, and loops over DbDataReader. We built this provider to remove that work.
How RepoDB helps
- Less plumbing. You get typed entities, expression-based queries and automatic mapping, and you can still drop down to raw SQL whenever you need to.
- Fast bulk loading. DuckDB is often used to load large datasets. Our Bulk Operations use DuckDB.NET’s native
DuckDBAppenderrather than inserting row by row. - Driver quirks already handled. DuckDB.NET returns
TIME,DATEandBLOBvalues as driver-specific types. Built-in property handlers map them toTimeSpan,DateTimeandbyte[]. Identity columns are emulated with sequences and read back throughRETURNING. - One API across databases. You use the same programming model as our SQL Server, PostgreSQL, MySQL, SQLite, Oracle, DB2, MariaDB, Firebird, ClickHouse, Vertica, SAP HANA and EnterpriseDB extensions.
The integration suites run against a real in-process DuckDB with no containers. Currently, 692 core tests and 590 Bulk Operations tests are passing.
Getting started
Install the packages:
> Install-Package RepoDb.DuckDb -Version 0.0.1-alpha
> Install-Package RepoDb.DuckDb.BulkOperations -Version 0.0.1-alpha
Initialize it once at startup:
GlobalConfiguration
.Setup()
.UseDuckDb();
CRUD
Insert
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var person = new Person { Name = "John Doe", Age = 30, CreatedDateUtc = DateTime.UtcNow };
var id = connection.Insert(person);
Query
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var person = connection.Query<Person>(e => e.Id == 10045).FirstOrDefault();
Update
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var person = connection.Query<Person>(e => e.Id == 10045).FirstOrDefault();
person.Age = 31;
var updatedRows = connection.Update(person);
Merge
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var person = new Person { Id = 10045, Name = "John Doe", Age = 32, CreatedDateUtc = DateTime.UtcNow };
var id = connection.Merge(person);
Delete
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var deletedRows = connection.Delete<Person>(10045);
Batch Operations
The InsertAll, UpdateAll, MergeAll and DeleteAll operations send multiple rows per round-trip. The number of rows per round-trip is controlled by batchSize.
InsertAll
The generated identities are set back on the entities.
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = GetPeople(1000);
var insertedRows = connection.InsertAll(people, batchSize: 100);
UpdateAll
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = connection.Query<Person>(e => e.Age < 18).AsList();
people.ForEach(p => p.Age += 1);
var updatedRows = connection.UpdateAll(people, batchSize: 100);
MergeAll
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = GetPeople(1000);
var mergedRows = connection.MergeAll(people, batchSize: 100);
DeleteAll
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = connection.Query<Person>(e => e.Age > 100);
var deletedRows = connection.DeleteAll(people);
Bulk Operations
Bulk operations come from RepoDb.DuckDb.BulkOperations. They load rows through DuckDB’s native appender, then apply the changes with a single set-based statement.
BulkInsert
Use ReturnIdentity to get the generated identities back on the entities.
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = GetPeople(100000);
var insertedRows = connection.BulkInsert(people,
identityBehavior: DuckDbBulkImportIdentityBehavior.ReturnIdentity);
BulkUpdate
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = GetPeopleToUpdate();
var updatedRows = connection.BulkUpdate(people);
BulkMerge
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = GetPeopleToMerge();
var mergedRows = connection.BulkMerge(people, qualifiers: e => new { e.Name });
BulkDelete
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var people = GetPeopleToDelete();
var deletedRows = connection.BulkDelete(people);
BulkDeleteByKey
using var connection = new DuckDBConnection("Data Source=analytics.duckdb");
var deletedRows = connection.BulkDeleteByKey("Person", new object[] { 10045, 10046, 10047 });
Every operation also has an Async counterpart. See the DuckDB Bulk Operations page for the details.
A preview, not a final release
RepoDb.DuckDb and RepoDb.DuckDb.BulkOperations are preview packages at v0.0.1-alpha, and their APIs and behavior may change before a stable release. Some known constraints apply. INTERVAL values with month or year parts are not supported, because of a driver limitation. Also, the async bulk methods currently run synchronously underneath, since DuckDBAppender has no async API. The limitations page lists the rest.
We would love to hear your feedback as you try it out. It directly shapes what we prioritize on the way to general availability.
~ Conceptualized by me. Reviewed and checked by AI. Refined and gatekept by me. ~
Please support us
Give a ⭐ to our Github repository |
Follow me at X / Twitter |
Connect with us at our official Teams Channel
Apache License 2.0 — Copyright © 2026 by Michael Camara Pendon / Create a Request