BulkInsert
This method inserts all rows from the client application into the database in bulk. It is supported for SAP HANA.
This page documents the SAP HANA-specific arguments and examples. For the SQL Server implementation, see BulkInsert (SQL Server).
Call Flow Diagram
The diagram below shows the flow when calling this operation.
flowchart TD
Client["Client<br/>(RepoDB)"] -->|BulkInsert| Source["Entities /<br/>DataTable /<br/>DbDataReader"]
Source --> Decision{"identityBehavior ==<br/>ReturnIdentity?"}
Decision -->|NO| Direct["Buffered row-by-row<br/>parameterized INSERT<br/>(one round trip per row)"]
Direct -->|Write| Table[("Target Table")]
Decision -->|YES| Pseudo["Create Pseudo Table<br/>(deterministic name)"]
Pseudo --> Staged["Buffered row-by-row<br/>parameterized INSERT"]
Staged -->|Write| PseudoTable[("Pseudo Table")]
PseudoTable --> PreAssign["Pre-assign identities:<br/>RowOrder + (MAX(identity)+1 - 1)"]
PreAssign --> Insert["INSERT INTO Target (...)<br/>SELECT ... FROM Pseudo"]
Insert --> Table
PreAssign -->|"SELECT identity FROM Pseudo<br/>ORDER BY row-order"| Client
PseudoTable -->|Drop| Cleanup(["Pseudo Table Dropped"])
Use Case
Use this method to insert rows against SAP HANA without hand-rolling the row-by-row loop yourself.
SAP HANA has no native bulk-load API and rejects a multi-row
INSERT ... VALUES (...), (...)list, so this “bulk” operation is a client-buffered loop of single-row, parameterizedINSERTstatements — one round trip per row, same as calling InsertAll yourself. The value over InsertAll is mainly API consistency with the other providers (mappings,identityBehavior, tracing) rather than a genuine round-trip reduction.
Rows are written straight to the target table. A pseudo (staging) table is only used when identityBehavior is set to ReturnIdentity (see below) — see Operations (SAP HANA) for the underlying mechanics.
Special Arguments
The mappings, bulkCopyTimeout, batchSize, identityBehavior and pseudoTableType arguments are available for this operation.
mappings (via SapHanaBulkInsertMapItem) defines explicit column mappings between the source properties and the destination columns, with an optional HanaDbType override per mapping. When omitted, columns are auto-mapped by name (case-insensitive). Mismatched source/destination CLR types throw an InvalidTypeException up front.
bulkCopyTimeout overrides the command timeout, in seconds, applied to each row’s INSERT. Unlike Firebird/Vertica, this argument genuinely takes effect here.
batchSize overrides how many rows are buffered client-side between flushes (default 500) — it does not change the number of round trips.
identityBehavior (via SapHanaBulkImportIdentityBehavior) controls whether newly generated identity values are set back on the data entities. Disabled (KeepIdentity) by default. Enabling this (ReturnIdentity) routes the operation through a pseudo table instead.
pseudoTableType (via SapHanaBulkImportPseudoTableType) controls the kind of staging table used when identityBehavior is ReturnIdentity.
The
DbDataReaderoverload has noidentityBehaviorargument — a forward-only, single-pass reader cannot be rewound to correlate generated identity values back onto a source row.
Identity Setting Alignment
When identityBehavior is ReturnIdentity, the library first queries the target table for MAX(identity) + 1 to determine the next value the real identity sequence would hand out, then updates every pseudo-table row’s identity column to RowOrder + (seed - 1) — since the pseudo table’s own row-order column is itself a gap-free identity starting at 1, this assigns exactly seed, seed + 1, … to the rows in their original load order, with no per-row round trip or procedural loop needed. The pre-assigned rows are then moved into the real table with a single INSERT INTO ... SELECT ... FROM Pseudo, and every value is read back afterward via a SELECT ordered by the same row-order column.
This reads the live maximum off the table’s own row data rather than a cached sequence counter, so it can never be stale — but it does leave a small race window against a concurrent writer to the same table between that read and the final
INSERT. Verify this end-to-end, especially under concurrent writers, before relying on it in production.
Usability
The following example defines a method that produces a list of Person objects, then bulk-inserts 10,000 rows into the Person table.
private IEnumerable<Person> GetPeople(int count = 1000)
{
for (var i = 0; i < count; i++)
{
yield return new Person
{
Name = $"Person-{i}",
Age = 30,
CreatedDateUtc = DateTime.UtcNow
};
}
}
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
var insertedRows = connection.BulkInsert(people);
}
To specify a batch size:
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
var insertedRows = connection.BulkInsert(people, batchSize: 100);
}
batchSizeonly controls the client-side buffer; each row is still its own round trip.
To return the newly generated identity values:
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
var insertedRows = connection.BulkInsert(people,
identityBehavior: SapHanaBulkImportIdentityBehavior.ReturnIdentity);
}
DataTable
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
var table = ConvertToDataTable(people);
var insertedRows = connection.BulkInsert("\"Person\"", table);
}
Dictionary/ExpandoObject
using (var sourceConnection = new HanaConnection(sourceConnectionString))
{
var result = sourceConnection.QueryAll("\"Person\"");
using (var destinationConnection = new HanaConnection(destinationConnectionString))
{
var insertedRows = destinationConnection.BulkInsert("\"Person\"", result);
}
}
DataReader
using (var sourceConnection = new HanaConnection(sourceConnectionString))
{
using (var reader = sourceConnection.ExecuteReader("SELECT * FROM \"Person\""))
{
using (var destinationConnection = new HanaConnection(destinationConnectionString))
{
var rows = destinationConnection.BulkInsert("\"Person\"", reader);
}
}
}
To bulk-insert via DataEntityDataReader:
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
using (var reader = new DataEntityDataReader<Person>(people))
{
var insertedRows = connection.BulkInsert("\"Person\"", reader);
}
}
Column Mappings
Add column mappings using the SapHanaBulkInsertMapItem class.
var mappings = new List<SapHanaBulkInsertMapItem>();
// Add the mappings
mappings.Add(new SapHanaBulkInsertMapItem("SourceId", "DestinationId"));
mappings.Add(new SapHanaBulkInsertMapItem("SourceName", "DestinationName"));
mappings.Add(new SapHanaBulkInsertMapItem("SourceAge", "DestinationAge"));
mappings.Add(new SapHanaBulkInsertMapItem("SourceCreatedDateUtc", "DestinationCreatedDateUtc"));
// Execute
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
var insertedRows = connection.BulkInsert(people,
mappings: mappings);
}
Targeting a Table
To target a specific table, pass the literal table name.
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
var insertedRows = connection.BulkInsert("\"Person\"", people);
}
Async Method
An equivalent BulkInsertAsync method is also available.
using (var connection = new HanaConnection(connectionString))
{
var people = GetPeople(10000);
var insertedRows = await connection.BulkInsertAsync(people);
}