DuckDB

Learn on how to work with DuckDB databases using RepoDB library.

RepoDB is a hybrid .NET ORM library for DuckDB, an embedded, in-process analytical database engine — there is no server to connect to. The project is hosted at Github and is licensed with Apache 2.0. It is built on top of DuckDB.NET.

ConnectionString is either a file path (Data Source=my.db) or Data Source=:memory:. DuckDB also uses $name as its parameter placeholder sigil in raw SQL text (not @name), as reflected in the Executing a Query examples below.

Installation

Install the library via NuGet using the Package Manager Console.

> Install-Package RepoDb.DuckDb

After installation, call the globalized setup method to initialize all dependencies for DuckDB.

GlobalConfiguration
    .Setup()
    .UseDuckDb();

Create a DB Table

DuckDB has no native AUTO_INCREMENT — a sequence-backed DEFAULT is used instead, and is detected as an identity column. The examples below assume the following table exists in the database.

CREATE SEQUENCE "person_id_seq" START 1;
CREATE TABLE "Person"
(
    "Id" BIGINT PRIMARY KEY DEFAULT nextval('person_id_seq'),
    "Name" VARCHAR NOT NULL,
    "Age" INTEGER NOT NULL,
    "CreatedDateUtc" TIMESTAMP NOT NULL
);

Create a .NET Model

The examples below assume the following model exists in the application.

public class Person
{
    public long Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public DateTime CreatedDateUtc { get; set; }
}

Creating a Record

To insert a row, use the Insert method.

var person = new Person
{
    Name = "John Doe",
    Age = 54,
    CreatedDateUtc = DateTime.UtcNow
};
using (var connection = new DuckDBConnection(ConnectionString))
{
    var id = connection.Insert(person);
}

To insert multiple rows, use the InsertAll operation.

var people = GetPeople(100);
using (var connection = new DuckDBConnection(ConnectionString))
{
    var rowsInserted = connection.InsertAll(people);
}

Insert returns the identity/primary column value, InsertAll returns the number of rows inserted, and both set the identity/primary property back onto the entity model (if present).

Querying a Record

To query a row, use the Query method.

using (var connection = new DuckDBConnection(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 DuckDBConnection(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 DuckDBConnection(ConnectionString))
{
    var id = connection.Merge(person);
}

By default, the primary column is used as a qualifier. Custom qualifiers can also be specified.

var person = new Person
{
    Name = "John Doe",
    Age = 57,
    CreatedDateUtc = DateTime.UtcNow
};
using (var connection = new DuckDBConnection(ConnectionString))
{
    var id = connection.Merge(person, qualifiers: (p => new { p.Name }));
}

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 DuckDBConnection(ConnectionString))
{
    var affectedRecords = connection.MergeAll<Person>(people);
}

Merge returns the identity/primary column value, MergeAll returns the number of rows affected, and both set the identity/primary property back if present.

Deleting a Record

To delete a row, use the Delete method.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var deletedRows = connection.Delete<Person>(1);
}

Other columns can also be used as qualifiers.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var deletedRows = connection.Delete<Person>(p => p.Name == "John Doe");
}

To delete all rows, use the DeleteAll method.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var deletedRows = connection.DeleteAll<Person>();
}

A list of primary keys can also be passed for targeted deletion.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var primaryKeys = new [] { 10045, 11001, ..., 12011 };
    var deletedRows = connection.DeleteAll<Person>(primaryKeys);
}

Delete and DeleteAll both return the number of rows affected.

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 DuckDBConnection(ConnectionString))
{
    var updatedRows = connection.Update<Person>(person);
}

Specific columns can also be targeted using a dynamic update.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var updatedRows = connection.Update("Person", new { Id = 1, Name = "James Doe" });
}

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 DuckDBConnection(ConnectionString))
{
    var updatedRows = connection.UpdateAll<Person>(people);
}

By default, the primary column is used as a qualifier. Custom qualifiers can also be specified.

var people = GetPeople(100);
people
    .AsList()
    .ForEach(p => p.Name = $"{p.Name} (Updated)");
using (var connection = new DuckDBConnection(ConnectionString))
{
    var updatedRows = connection.UpdateAll<Person>(people,
        qualifiers: (p => new { p.Name }));
}

Update and UpdateAll both return the number of rows affected.

Executing a Query

To execute a non-query statement, use the ExecuteNonQuery method.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var sql = "UPDATE \"Person\" SET \"Name\" = $Name WHERE \"Id\" = $Id;";
    var affectedRecords = connection.ExecuteNonQuery(sql, new { Name = "John", Id = 1 });
}

To execute a query and return mapped objects, use the ExecuteQuery method.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var sql = "SELECT * FROM \"Person\" WHERE (\"Id\" = $Id);";
    var people = connection.ExecuteQuery<Person>(sql, new { Id = 1 });
    /* Process the results here */
}

To execute a query and return a scalar value, use the ExecuteScalar method.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var sql = "SELECT COUNT(*) FROM \"Person\";";
    var count = connection.ExecuteScalar<int>(sql);
}

To execute a query and return a DbDataReader, use the ExecuteReader method.

using (var connection = new DuckDBConnection(ConnectionString))
{
    var sql = "SELECT * FROM \"Person\";";
    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 DuckDBConnection(ConnectionString))
{
    var sql = "SELECT \"Name\" FROM \"Person\";";
    var names = connection.ExecuteQuery<string>(sql);
}

Returns an IEnumerable object.