AuroraDB MySqlConnector
For Aurora MySQL, the underlying implementation is leveraging AuroraDbBulkCopy, a LOAD DATA LOCAL INFILE-based bulk writer from RepoDb.Connector.AuroraDb.MySqlConnector.
For Aurora MySQL, the underlying implementation is leveraging AuroraDbBulkCopy, a LOAD DATA LOCAL INFILE-based bulk writer from RepoDb.Connector.AuroraDb.MySqlConnector.
For BulkInsert, the entities/rows are written straight to the target table — unless AuroraDbBulkImportIdentityBehavior.ReturnIdentity is requested, in which case a staging (pseudo) table is used instead. For BulkDelete, BulkDeleteByKey, BulkMerge and BulkUpdate, a pseudo table is always used. MySQL has neither a native MERGE statement nor a session-scoped identity sequence object, so this family’s mechanics differ meaningfully from a Postgres-wire provider’s: a merge is split into a plain UPDATE ... INNER JOIN (matched rows) plus an INSERT ... SELECT guarded by a LEFT JOIN ... WHERE ... IS NULL anti-join (unmatched rows), and a to-be-returned identity value is pre-assigned into the pseudo table via a session user variable — seeded from MAX(identityColumn) + 1 on the real table — rather than read back from an AUTO_INCREMENT sequence after the fact.
The data is brought together from the client application into the database server (at one-go). It then gets processed together at the same time.
The other bulk operations can be optimized further by targeting the underlying table indexes (via qualifiers). Pass a list of Field objects when calling the operations — an index on the pseudo table’s qualifier columns is also created automatically to speed up the matching statements.
Pseudo Table Type
The AuroraDbBulkImportPseudoTableType enum lets you choose between a session-private temporary table (Memory), an ordinary heap table (Physical), or let the library decide based on row count (Auto, the default — Physical at 5,000 rows or more, otherwise Memory).
As currently implemented, this argument has no effect — every bulk call resolves to
Physicalregardless of the value passed. See AuroraDbBulkImportPseudoTableType for details.
Supported Objects
Below are the following objects supported by the bulk operations.
- System.DataTable
- System.Data.Common.DbDataReader
- IEnumerable<T>
- ExpandoObject
- IDictionary<string, object>
Operation SQL Statements
Once all the data is in the staging (pseudo) table, the correct SQL statement is used to cascade the changes towards the original table. The pseudo table is always created via DROP TABLE IF EXISTS ...; CREATE TABLE ... ( `__RepoDbBulkRowOrder__` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY) AS SELECT ... FROM real WHERE (1 = 0) — a surrogate ordering column standing in for the row order MySQL has no ROWID-style pseudo-column to express.
BulkInsert writes directly into the target table and skips the staging table entirely — unless
identityBehavioris set toReturnIdentity, in which case a staging table is used first (see below).
For BulkInsert (with ReturnIdentity)
> SET @repodb_seq := (nextIdentityValue - 1);
> UPDATE "PseudoTempTable" SET "Identity" = (@repodb_seq := @repodb_seq + 1);
> INSERT INTO "OriginalTable" (Field1, Field2, ...) SELECT Field1, Field2, ... FROM "PseudoTempTable";
> SELECT "Identity" AS "Result" FROM "PseudoTempTable" ORDER BY "__RepoDbBulkRowOrder__";
For BulkDelete / BulkDeleteByKey
> DELETE T FROM "OriginalTable" T INNER JOIN "PseudoTempTable" S ON (T.QualifierField1 = S.QualifierField1);
For BulkMerge
Without identityBehavior: ReturnIdentity:
> UPDATE "OriginalTable" T INNER JOIN "PseudoTempTable" S ON (T.QualifierField1 = S.QualifierField1)
> SET T.Field3 = S.Field3, T.Field4 = S.Field4;
>
> INSERT INTO "OriginalTable" (Field1, Field2, ...)
> SELECT S.Field1, S.Field2, ... FROM "PseudoTempTable" S
> LEFT JOIN "OriginalTable" T ON (T.QualifierField1 = S.QualifierField1)
> WHERE T.QualifierField1 IS NULL;
With identityBehavior: ReturnIdentity, matched rows first copy their existing identity onto the pseudo row, unmatched rows get a pre-assigned one (same session-variable technique as BulkInsert), then the same update+insert pair runs, followed by a final SELECT ... ORDER BY reporting every row’s identity — see Identity Setting Alignment below.
For BulkUpdate
> UPDATE "OriginalTable" T INNER JOIN "PseudoTempTable" S ON (T.QualifierField1 = S.QualifierField1)
> SET T.Field3 = S.Field3, T.Field4 = S.Field4;
Unlike BulkMerge, staged rows with no matching target row are left as-is, not inserted.
Special Arguments
The arguments below are available on most operations.
| Argument | Description |
|---|---|
qualifiers |
Defines the fields used to match existing rows, corresponding to the WHERE/JOIN ON clause. Defaults to the primary (or identity) key when not provided. |
identityBehavior |
Via AuroraDbBulkImportIdentityBehavior, controls whether the identity property is kept as-is, or whether the newly generated identity values are returned back to the entities after BulkInsert or BulkMerge. |
pseudoTableType |
Via AuroraDbBulkImportPseudoTableType, currently accepted but not honored — see Pseudo Table Type above. |
batchSize |
Overrides the number of rows sent to the server per batch. When not set, all items are sent at once. |
Identity Setting Alignment
When identityBehavior is set to ReturnIdentity, MySQL’s lack of a per-row SEQUENCE.NEXTVAL-style call means the identity value cannot simply be read back after the insert the way a Postgres-wire provider does. Instead, it is pre-assigned before the real INSERT:
- A live
SELECT MAX(identityColumn) + 1 FROM realseeds the next value — read fresh off the table’s actual row data each time (notinformation_schema.TABLES.AUTO_INCREMENT, which MySQL 8 caches for up toinformation_schema_stats_expiryseconds and can return a stale, already-used counter). SET @repodb_seq := (seed - 1);followed byUPDATE "PseudoTempTable" SET identity = (@repodb_seq := @repodb_seq + 1);assigns a distinct, increasing value to every staged row via a session user variable.- The rows are copied into the real table as plain
INSERT ... SELECT, carrying their pre-assigned identity values as literals — MySQL always accepts an explicit value into anAUTO_INCREMENTcolumn, and auto-advances its internal counter past it, so this doesn’t create a future collision. - A final
SELECT identity AS "Result" FROM "PseudoTempTable" ORDER BY "__RepoDbBulkRowOrder__"reports every value back in the original bulk-load order.
This requires
AllowUserVariables=Trueon the connection string —AuroraDbConnectiondefaults this tofalse. PlainBulkInsert(withoutReturnIdentity),BulkUpdate,BulkDeleteandBulkDeleteByKeynever touch a session variable and don’t need this flag.
Reading the seed and assigning it are two separate round trips, leaving a small race window against a concurrent writer to the same table — there is no table-level locking here (
LOCK TABLESwould silently commit any transaction already open on the connection). For strict correctness under concurrent writers, avoid overlappingReturnIdentitybulk calls against the same table.
BatchSize
All the provided operations have a batchSize argument that lets you override the number of rows wired-up to the server per batch. By default it is null, meaning all items are sent together in one-go.
Use this argument if you wish to optimize the operation based on certain situations.
- Network Latency
- Infrastructure
- No. of Columns
- Type of Data
Async Methods
All the provided synchronous operations have an equivalent asynchronous (Async) counterpart.
BulkDelete
using (var connection = new AuroraDbConnection(connectionString))
{
var people = connection.Query<Person>(e => e.IsActive == false);
var deletedRows = connection.BulkDelete<Person>(people);
}
BulkDeleteByKey
using (var connection = new AuroraDbConnection(connectionString))
{
var primaryKeys = new object[] { 10045, 10046, 10047 };
var deletedRows = connection.BulkDeleteByKey("Person", primaryKeys);
}
BulkInsert
using (var connection = new AuroraDbConnection(connectionString))
{
var people = GetPeople(10000);
var insertedRows = connection.BulkInsert(people);
}
BulkMerge
using (var connection = new AuroraDbConnection(connectionString))
{
var mergedRows = connection.BulkMerge(people);
}
BulkUpdate
using (var connection = new AuroraDbConnection(connectionString))
{
var updatedRows = connection.BulkUpdate(people);
}