DuckDbBulkAppender

Bulk writes rows into a DuckDB table using the native DuckDBAppender, used internally by the DuckDB bulk operations.

This class bulk-writes rows into a DuckDB table using the native DuckDBAppender (via DuckDBConnection.CreateAppender()). When every source column maps directly onto a destination column, rows are appended straight into the target table; otherwise, the mapped columns are staged into a temporary table first and copied over with a single INSERT ... SELECT ... ORDER BY rowid.

It is part of RepoDb.DuckDb.BulkOperations and implements IDisposable.

Properties

Name Description
DestinationTableName The target table to write to.
BulkCopyTimeout The command timeout, in seconds, applied to the staging/copy commands. Zero (the default) uses the driver default.
BatchSize The number of rows appended before the underlying DuckDBAppender is flushed and re-created. Zero (the default) flushes once at the end.
Transaction The DuckDBTransaction to write under.
ColumnMappings A DuckDbBulkAppenderColumnMappingCollection of explicit source-to-destination column mappings. When left empty, columns are mapped by name (case-insensitive) against the columns that exist on both sides.

Create a new Instance

using (var connection = new DuckDBConnection(ConnectionString))
{
    connection.Open();

    using var appender = new DuckDbBulkAppender(connection)
    {
        DestinationTableName = "Person",
        BatchSize = 1000,
        BulkCopyTimeout = 120
    };
}

Usability

Call WriteToServer()/WriteToServerAsync() with either an IDataReader or a DataTable to write rows. Both overloads return the number of rows written.

Via an IDataReader (e.g. DataEntityDataReader):

using (var connection = new DuckDBConnection(ConnectionString))
{
    connection.Open();

    var people = GetPeople(100000);
    using var reader = new DataEntityDataReader<Person>(people);
    using var appender = new DuckDbBulkAppender(connection)
    {
        DestinationTableName = "Person"
    };

    var writtenRows = appender.WriteToServer(reader);
}

Via a DataTable:

using (var connection = new DuckDBConnection(ConnectionString))
{
    connection.Open();

    var table = GetPeopleAsDataTable();
    using var appender = new DuckDbBulkAppender(connection)
    {
        DestinationTableName = "Person"
    };

    var writtenRows = appender.WriteToServer(table);
}

Optionally, restrict a DataTable write to rows in a specific DataRowState (e.g. only Added rows):

var writtenRows = appender.WriteToServer(table, DataRowState.Added);

With explicit column mappings:

using (var connection = new DuckDBConnection(ConnectionString))
{
    connection.Open();

    using var appender = new DuckDbBulkAppender(connection)
    {
        DestinationTableName = "Person"
    };
    appender.ColumnMappings.Add("FirstName", "FName");
    appender.ColumnMappings.Add("LastName", "LName");

    var people = GetPeople(100000);
    using var reader = new DataEntityDataReader<Person>(people);
    var writtenRows = appender.WriteToServer(reader);
}

Every value handed to the appender is converted to the destination column’s CLR type first (e.g. DateTime → DateOnly, TimeSpan → TimeOnly, string ↔ Guid) before it is bound — see DuckDbTypeNameToClientTypeResolver for how a column’s CLR type is itself resolved.