Installation To use the DuckDB C# client, install the
DuckDB.NET.Data.FullNuGet package.The latest stable version of the DuckDB C# client is 1.5.6.
DuckDB.NET is an open-source ADO.NET provider for DuckDB. It also includes a low-level .NET wrapper around the DuckDB C API. The client is maintained by Giorgi Dalakishvili. For the full documentation, see the DuckDB.NET website.
Installation
DuckDB.NET is distributed in four NuGet packages. The ADO.NET provider (DuckDB.NET.Data) is built on top of the low-level bindings library (DuckDB.NET.Bindings) and is the recommended way to use DuckDB from .NET. Both are available in a Full variant, which bundles the DuckDB native library, and in a managed-only variant, which does not include the native library.
| Package | ADO.NET provider | Includes DuckDB native library |
|---|---|---|
DuckDB.NET.Bindings |
no | no |
DuckDB.NET.Bindings.Full |
no | yes |
DuckDB.NET.Data |
yes | no |
DuckDB.NET.Data.Full |
yes | yes |
To install the ADO.NET provider along with the DuckDB native library, run:
dotnet add package DuckDB.NET.Data.Full
The packages target .NET 8.0 and later.
Basic Usage
DuckDB.NET follows the ADO.NET provider model. To run SQL statements, create a DuckDBConnection, open it, and create a command. Then, execute the command using one of the following methods:
ExecuteNonQuery: executes statements that do not return results, such as DDL statements andINSERT,UPDATE, orDELETEstatements.ExecuteScalar: executes a query that returns a single scalar value.ExecuteReader: executes a query and returns aDuckDBDataReader, which can be used to consume the result set.
using DuckDB.NET.Data;
using var connection = new DuckDBConnection("Data Source=file.db");
connection.Open();
using var command = connection.CreateCommand();
command.CommandText = "CREATE TABLE integers (foo INTEGER, bar INTEGER);";
command.ExecuteNonQuery();
command.CommandText = "INSERT INTO integers VALUES (3, 4), (5, 6), (7, NULL);";
command.ExecuteNonQuery();
command.CommandText = "SELECT count(*) FROM integers;";
var count = command.ExecuteScalar();
Console.WriteLine($"Number of rows: {count}");
command.CommandText = "SELECT foo, bar FROM integers;";
using var reader = command.ExecuteReader();
while (reader.Read())
{
var foo = reader.GetInt32(0);
var bar = reader.IsDBNull(1) ? "NULL" : reader.GetInt32(1).ToString();
Console.WriteLine($"{foo} {bar}");
}
Parameterized Queries
To protect against SQL injection, use parameterized queries instead of string concatenation. DuckDB.NET supports all three prepared statement syntaxes: auto-incremented (?), positional ($1), and named ($param) parameters.
using var command = connection.CreateCommand();
command.CommandText = "SELECT * FROM integers WHERE foo >= $minimum_foo;";
command.Parameters.Add(new DuckDBParameter("minimum_foo", 5));
using var reader = command.ExecuteReader();
Multiple Statements
To execute multiple statements in a single command, separate them with semicolons. If the statements return results, use the NextResult method of the data reader to move to the next result set.
Streaming Mode
By default, DuckDB.NET executes queries in materialized mode, which fetches the entire result set into memory. To reduce memory usage for large results, set the command's UseStreamingMode property to true. In streaming mode, DuckDB fetches the result set chunk by chunk, which may result in slower query execution.
command.UseStreamingMode = true;
Connection Strings
DuckDB.NET connection strings follow the standard ADO.NET syntax, i.e., a semicolon-separated list of keywords and values. The DataSource (or Data Source) keyword specifies the path to the database file. If the file does not exist, it is created when the connection is first opened.
To use an in-memory database, set the data source to :memory:. Each in-memory connection creates a new, isolated database. To share an in-memory database between connections, use :memory:?cache=shared.
All other keywords are treated as DuckDB configuration options, which are set when the database is opened. Options that cannot be set at startup can be changed using the SET statement after opening the connection.
| Connection string | Description |
|---|---|
DataSource=:memory: |
Connect to a new in-memory database |
DataSource=:memory:?cache=shared |
Connect to a shared in-memory database |
DataSource=train_services.db |
Connect to train_services.db |
DataSource=train_services.db;access_mode=READ_ONLY |
Connect to train_services.db in read-only mode |
DataSource=train_services.db;threads=8;memory_limit=10GB |
Connect to train_services.db, limit the number of threads to 8 and the memory usage to 10 GB |
Connection strings can also be created with the DuckDBConnectionStringBuilder class, which has typed properties for commonly used configuration options:
var builder = new DuckDBConnectionStringBuilder
{
DataSource = "train_services.db",
AccessMode = DuckDBAccessMode.ReadOnly,
Threads = 8,
MemoryLimit = "10GB"
};
using var connection = new DuckDBConnection(builder.ConnectionString);
connection.Open();
Further Features
DuckDB.NET supports several further features, which are described in its documentation:
- Apache Arrow result streaming
- Bulk data loading using the appender
- Type mappings between DuckDB and .NET types
- Composite types and enums
- User-defined functions, including scalar and table functions written in C#
To build DuckDB extensions in C#, see the DuckDB.ExtensionKit project and its introductory blog post.
Getting Help
To ask questions about DuckDB.NET, join the DuckDB Discord and post your question in the dotnet channel. To report a bug, open an issue in the DuckDB.NET repository and include a minimal, reproducible example.