Clients & tools
.NET (ADO.NET)

.NET (ADO.NET)

Thunderduck.Data is an ADO.NET provider for .NET 8 and later. It works with plain ADO.NET, Dapper and DataTable, and adds an async client for submitting, waiting and paging yourself. It has no third-party dependencies.

dotnet add package Thunderduck.Data

The package is published on NuGet as Thunderduck.Data (opens in a new tab). Versions, release notes and other install commands (PackageReference, Paket) are on that page.

Connection

Host=api.thunderduck.ai;Token=tdk_…

Authenticate with an API token created in the console (Settings → API tokens). Optional keys:

  • Timeout: query timeout in seconds, default 300;
  • PageSize: rows per fetch, default 1000;
  • PollInterval: poll interval in ms, default 1000;
  • Ssl: HTTPS, default true;
  • Port.

Open() validates the token. After that, ConnectionString no longer contains it.

Querying

using Thunderduck.Data;
 
await using var conn = new ThunderduckConnection($"Host=api.thunderduck.ai;Token={token}");
await conn.OpenAsync();
 
await using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT order_id, total_amount FROM sales_pg.public.orders WHERE total_amount > @min";
cmd.Parameters.AddWithValue("min", 100m);
 
await using var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
    Console.WriteLine($"{reader.GetInt64(0)}: {reader.GetDecimal(1)}");

With Dapper:

var orders = await conn.QueryAsync<Order>(
    "SELECT order_id AS Id, total_amount AS Total FROM sales_pg.public.orders WHERE total_amount > @min",
    new { min = 100m });

Addressing

Exactly as everywhere else — three parts, with the middle part depending on the catalog type:

SELECT * FROM sales_pg.public.orders LIMIT 100;
SELECT * FROM lake.default.events LIMIT 100;

conn.GetSchema("Tables") lists your catalogs' tables. The Catalogs, Schemas and Columns collections work the same way, with restrictions. These calls read metadata Thunderduck already collected and run no SQL.

Cancellation

Cancelling the CancellationToken, calling DbCommand.Cancel() or hitting the timeout each stop the wait and cancel the query on the server. Nothing keeps running after your caller gives up.

Types

Values come back as the .NET type you'd expect:

DuckDB.NET
integer typesint, long, …
DECIMALdecimal, exact
DATEDateTime (or GetFieldValue<DateOnly>)
TIMETimeSpan (or GetFieldValue<TimeOnly>)
TIMESTAMPDateTime
TIMESTAMPTZDateTime in UTC (or GetFieldValue<DateTimeOffset>)
UUIDGuid
BLOBbyte[]

Nested types (LIST, STRUCT, MAP) come back as JSON strings. You can also read them with GetFieldValue<JsonElement>().

Limits

  • Each query runs as a job, so expect seconds before the first row. Don't use it for tight request/response loops.
  • There are no transactions, and RecordsAffected is -1.
  • Parameters are inlined into the SQL by the client as typed DuckDB literals; the server doesn't bind them.
  • Results are read from the stored result. Stored results expire after a day, and reading an expired one throws ThunderduckException with Kind = ResultExpired.