SAP HANA (Bulk)

This page contains the latest information of the releases of RepoDb.SapHana.BulkOperations library.

View the NuGet package here or download it directly here.

RepoDb.SapHana.BulkOperations (v1.17.0-alpha3)

Released: October 10, 2026

  • Updated the Sap.Data.Hana.Net.v6.0 package reference to v2.30.27.
  • Referenced the RepoDb package v1.17.0-beta2.
  • Referenced the RepoDb.SapHana package v1.17.0-alpha3.

RepoDb.SapHana.BulkOperations (v1.17.0-alpha2)

Released: October 9, 2026

  • Enhancement: Made the package NativeAOT and trimming compatible (IsAotCompatible), replacing the reflection-based and dynamic code paths with AOT-safe equivalents.
  • Referenced the RepoDb package v1.17.0-beta1.
  • Referenced the RepoDb.SapHana package v1.17.0-alpha2.

RepoDb.SapHana.BulkOperations (v1.17.0-alpha1)

Released: October 1, 2026

  • Adopted the Roslyn and Meziantou static code analyzers across the codebase and applied the resulting fixes — including ConfigureAwait(false) on async calls, ArgumentNullException over NullReferenceException, and other code-quality corrections. #1328 #1331
  • Updated the NuGet package icon to the new logo and refreshed the README with a consistent header, description and a link to the get started tutorial.
  • Introduced SapHanaBulkColumnMappingsBehavior (Automatic, Strict, StrictBypass) and the BulkColumnMappingsBehavior setting on SapHanaBulkDbSetting — opt into fail-fast validation of the source-to-destination column mapping for BulkInsert, BulkMerge, BulkUpdate and BulkDelete when no explicit mappings are passed. #1360
  • Replaced the row-by-row SapHanaCommandBatcher with Async-Over-Sync as the default write strategy of the Async bulk overloads. The SAP HANA ADO.NET driver has no asynchronous bulk-copy API, so the genuine, synchronous HanaBulkCopy load is now offloaded onto a background thread via Task.Run — the calling thread is not blocked, and the load runs as one real bulk operation rather than one INSERT round trip per row, making it by far the fastest option for any non-trivial row count. The trade-off is that a thread-pool thread is held for the duration of the load, as the driver offers no true async I/O. #1363
  • Changed the default of WriteToServerExecution on SapHanaBulkDbSetting from SapHanaCommandBatcher to AsyncOverSync. SapHanaCommandBatcher is now an explicit opt-in compatibility fallback, only for environments where HanaBulkCopy itself cannot be used. See SapHanaWriteToServerExecution.
  • Referenced the RepoDb package v1.17.0-alpha3.
  • Referenced the RepoDb.SapHana package v1.17.0-alpha1.

RepoDb.SapHana.BulkOperations (v1.16.0)

Released: September 9, 2026

Version bump only, aligning with the RepoDB ecosystem-wide v1.16.0 release — no functional changes since v1.0.0.

  • Referenced the RepoDb package v1.16.0.
  • Referenced the RepoDb.SapHana package v1.16.0.

RepoDb.SapHana.BulkOperations (v1.0.0)

Released: September 8, 2026

First general availability (non-preview) release of the SAP HANA Bulk Operations package, promoting the v0.0.1-alpha preview below to stable. No functional changes since v0.0.1-alpha — see that entry for the full feature set and known limitations.

RepoDb.SapHana.BulkOperations (v0.0.1-alpha) - Preview

Released: TBA

New

First release of the bulk operations extension for RepoDb.SapHana.

Verification status: implemented and reviewed, but not yet exercised against a live SAP HANA instance. Verify the pseudo-table lifecycle, the concurrency caveat below, and the identity-assignment arithmetic end-to-end before relying on this package in production.

Architectural note: SAP HANA has no native bulk-load API to build on, and its SQL parser rejects a multi-row INSERT ... VALUES (...), (...) list. Every write here is a client-buffered loop of single-row, parameterized INSERT statements — one round trip per row — against the real or pseudo table.

What’s included

  • BulkInsert, BulkMerge, BulkUpdate, BulkDelete and BulkDeleteByKey, each with an Async overload, callable against a HanaConnection with an entity list, a DataTable, or an IDataReader (BulkDeleteByKey takes a target table name and a list of primary key values directly, rather than an entity type parameter).
  • A row-by-row write pipeline (an internal ColumnFilteredDataReader plus a buffered INSERT loop) that buffers rows client-side (batchSize, default 500) to control memory/flush frequency. bulkCopyTimeout applies here as each row’s CommandTimeout.
  • SapHanaBulkImportIdentityBehavior (KeepIdentity default, ReturnIdentity) — controls whether identity values are sent as-is or read back. ReturnIdentity pre-assigns identities client-side (MAX(identity) + 1, offset by the pseudo table’s own row-order column) before moving rows into the real table with a single INSERT ... SELECT, then reads them back ordered by that same row-order column.
  • SapHanaBulkImportPseudoTableType (Auto, Memory, Physical) — selects the staging-table strategy backing every operation.
  • SapHanaBulkInsertMapItem — explicit source-to-destination column mapping, with an optional HanaDbType override. Explicit mappings are also type-checked up front: an incompatible source/destination CLR type pairing (other than the allowed Guid↔string and integral-to-integral widenings) throws an InvalidTypeException immediately, rather than failing inside SAP HANA.
  • SapHanaTraceKeys — the tracing key constants for the five bulk operations, for use with ITrace.
  • SapHanaCommandBatcher and SapHanaCommandBatcherColumnMappingCollection — since HanaBulkCopy has no native async API, the Async overloads write through this HanaCommand-based row batcher instead, giving genuine asynchronous execution rather than a synchronous HanaBulkCopy call offloaded onto a thread-pool thread via Task.Run. The sync overloads still use HanaBulkCopy directly.
  • SapHanaBulkDbSetting and ISapHanaBulkDbSetting — an opt-in SapHanaDbSetting subclass (and its interface) exposing WriteToServerExecution (via SapHanaWriteToServerExecution), letting the Async overloads fall back to the synchronous HanaBulkCopy path instead of SapHanaCommandBatcher. Not registered by default — pass an instance to UseSapHana() or register it via DbSettingMapper to opt in.
  • SapHanaGuidToStringPropertyHandler — maps a Guid entity property to/from an NVARCHAR(36) column, since SAP HANA has no native GUID type. Ships in this package (not the core RepoDb.SapHana provider), but is a plain IPropertyHandler<string, Guid> usable with any operation, not just the bulk ones.
  • BulkMerge uses a real, single-statement ANSI MERGE when identityBehavior is KeepIdentity. When ReturnIdentity is requested, a three-step sequence runs instead: (1) copy the existing identity value onto every pseudo row that already matches a target row, (2) assign a fresh, gap-free identity — via a correlated COUNT(*), deliberately plain portable ANSI SQL rather than a window function — to every unmatched row, then (3) run the MERGE with the identity column inserted explicitly. See BulkMerge for details.
  • BulkUpdate/the update half of BulkMerge uses a correlated-subquery SET (col1, col2, ...) = (SELECT ...) form guarded by WHERE EXISTS, since SAP HANA has no multi-table UPDATE ... JOIN.
  • Dropping a pseudo table swallows SAP HANA’s “invalid table name” native error (259) — HANA’s DROP TABLE has no IF EXISTS clause, so a drop against a pseudo table that was never created (or already dropped) is expected to fail this specific way, and only this way.

Known limitations (v1)

  • Pseudo table names are deterministic, not per-call-unique — built from the target table name, the operation, and the pseudoTableType (e.g. PhysicalPersonMerge). Combined with the next limitation, this means two concurrent bulk calls of the same operation against the same table can genuinely interfere with each other’s staged rows.
  • Auto and Memory both currently resolve to Physical at runtime — the internal resolution logic returns Physical on every outcome, so the session-isolated LOCAL TEMPORARY path Memory advertises is not implemented yet. Avoid running concurrent SAP HANA bulk operations of the same kind against the same table until this is resolved.
  • Every “bulk” write is really a client-buffered loop of single-row INSERT statements — one round trip per row. batchSize (default 500) only controls how many rows are buffered between flushes; it does not reduce round trips.
  • BulkInsert/BulkMerge’s ReturnIdentity identity pre-assignment reads the live MAX(identity) off the table’s own row data (not a cached sequence counter), which can never be stale but does leave a small race window against a concurrent writer to the same table between that read and the final INSERT/MERGE.
  • Per the library’s own source comments, BulkMerge’s correlated-COUNT rank computation for assigning fresh identities to unmatched rows in the ReturnIdentity path is the least-verified statement in the whole provider. Verify it specifically, especially under concurrent writers, before relying on it in production.
  • The DbDataReader overloads of BulkInsert and BulkMerge have no identityBehavior argument — a forward-only, single-pass reader cannot be rewound to correlate generated identity values back onto a source row.
  • BulkDeleteByKey takes a tableName string directly (BulkDeleteByKey<TPrimaryKey>(connection, tableName, primaryKeys, ...)) rather than an entity type parameter.
  • This package inherits every Known limitation of RepoDb.SapHana itself (e.g. no table hints, Merge requiring a known primary key value up front).

  • Referenced the RepoDb package v1.16.0.
  • Referenced the RepoDb.SapHana package v0.0.1-alpha.
  • Referenced the Sap.Data.Hana.Net.v6.0 package v2.29.25.