RepoDB v1.16.0 — Nine New Database Providers, MySQL Bulk, and a Platform Shift

12 minute read

Published:

We are very excited to announce the availability of RepoDB v1.16.0. The phase 1 of our shift to be a Data Movement Platform! It’s the biggest release in the project’s history! 🔥

Until before this release, RepoDB supported only the SQL Server, PostgreSQL, MySQL, and SQLite DB providers. This release adds nine brand-new database providers with their Bulk capabilities. It also brings native Bulk operations to MySQL and MySqlConnector for the first time. And it tightens core type-conversion behavior in ways that are intentionally breaking for a small number of call sites.

This isn’t just a bigger provider list. It’s RepoDB moving from a hybrid-ORM into a genuine universal data connectivity platform. See the roadmap article for the bigger picture.

~ The image above is generated by ChatGPT. ~

🧩 The shape of this release

Every new provider follows the same pattern. A core package (RepoDb.) and a matching RepoDb..BulkOperations package. Each one ships an IDbSetting, a StatementBuilder, a DbHelper, and property handlers for that provider's type quirks.

Most of these are early versions, not yet fully mature. Several are explicitly flagged as implemented and reviewed, but not yet exercised against a live instance. Read a provider’s limitations before putting it in production.

⚡ How to work with the new RepoDB

Nine new providers isn’t just a bigger list to pick from — it’s nine new endpoints you can now move data between, using the same handful of lines regardless of which two you pick.

The trick is IDataReader. BulkInsert doesn’t require a fully-loaded list of entities — it also accepts a data reader, and a reader is a forward-only stream, never a fully materialized collection. Point one connection’s ExecuteReader straight into another connection’s BulkInsert, and rows flow from source to destination one at a time. Nothing about that pipeline changes whether the table has a hundred rows or a few hundred million — the memory footprint stays flat either way.

using var source = new OracleConnection(oracleConnectionString);
source.Open();

using var sqlServer = new SqlConnection(sqlServerConnectionString);
using (var reader = source.ExecuteReader("SELECT * FROM Customer"))
    sqlServer.BulkInsert<Customer>(reader, tableName: "Customer");

using var mariaDb = new MariaDbConnection(mariaDbConnectionString);
using (var reader = source.ExecuteReader("SELECT * FROM Customer"))
    mariaDb.BulkInsert<Customer>(reader, tableName: "Customer");

A reader is single-pass, so each destination opens its own ExecuteReader against the source rather than sharing one across both calls. Swap OracleConnection and SqlConnection for any pair from this release — say, Db2Connection reading straight into a VerticaConnection’s BulkInsert — and the shape of the code doesn’t change. That’s the point of shipping the same IDbSetting/StatementBuilder/DbHelper pattern across every provider: one programming model, nine new places to point it at.

Entity-based movement, when you need to filter first

Sometimes you don’t want everything from the source — you want the subset that matches a condition. BulkInsert accepts a typed entity list just as readily as a reader, so you can Query the source with a filter and hand the result straight to BulkInsert on the destination:

using var source = new OracleConnection(oracleConnectionString);
source.Open();

var activeCustomers = source.Query<Customer>(c =>
    c.IsActive == true && c.CreatedDateUtc >= DateTime.UtcNow.AddYears(-1));

using var sqlServer = new SqlConnection(sqlServerConnectionString);
sqlServer.BulkInsert(activeCustomers);

The filter runs on the source, so only matching rows ever cross the wire. This path trades the reader’s flat, zero-footprint streaming for something else: activeCustomers is a real, typed, in-memory list — worth it when you need to filter, inspect, or transform the entities before they land in BulkInsert, and the filtered set is small enough to hold in memory. For a full, unfiltered table at massive scale, the IDataReader approach above is still the one that keeps memory flat.

🌐 New database providers

Each one ships as a core package plus a matching .BulkOperations package. Most are early versions and not so mature yet — check each package’s own limitations before production use.

ProviderBatchBulkSyncAsyncClassVersions supported
ClickHouseClickHouseBulkCopy / OPTIMIZE_ON_INSERT.NET 8/9/10
DB2DB2BulkCopy Native (Sync) / Db2BulkArrayBinder (Async Native)Db2 LUW 10.5+, .NET 8/9/10
EnterpriseDBEDBBulkCopy (via RepoDb.Connector.EnterpriseDb) via Npgsql NativeEDB Postgres Advanced Server, .NET 8/10
FirebirdFirebirdCommandBatcher (via FbBatchCommand) NativeFirebird 3.0+, .NET Standard 2.0, .NET 8/9/10
MariaDBMariaDbBulkCopy (via RepoDb.Connector.MariaDb) Native.NET Standard 2.0, .NET 8/9/10
MariaDB ConnectorMariaDbBulkCopy (via RepoDb.Connector.MariaDbConnector) Native.NET Standard 2.0, .NET 8/9/10
OracleOracleBulkCopy Native / OracleBulkArrayBinder (Async Native)Oracle 12c+, .NET 8/9/10
SAP HANA⚠️HanaBulkCopy Native (Sync) / AsyncOverAsync or SapHanaCommandBatcher.NET 8/9/10
Vertica⚠️VerticaBulkCopy (via VerticaCopyStream) / Async over Sync.NET Standard 2.0, .NET 8/9/10

Install only one of RepoDb.MariaDb or RepoDb.MariaDbConnector per project — both declare identically-named types, and referencing both is a hard compile error.

📦 New Bulk operations for MySQL and MySqlConnector

Two brand-new packages bring native Bulk operations to MySQL for the first time — BulkInsert, BulkMerge, BulkUpdate, BulkDelete, and BulkDeleteByKey, all with async counterparts.

RepoDb.MySql.BulkOperations (v1.0.0) targets MySqlConnection from MySql.Data. Since MySql.Data ships no streaming bulk-copy API, every row-load runs through an internal MySqlBulkCopy — a LOAD DATA LOCAL INFILE-based stand-in built on MySqlBulkLoader, serializing rows to a temp tab-delimited file. It requires AllowLoadLocalInfile=True;AllowUserVariables=True; on the connection string, plus the server’s local_infile global variable enabled. One important caveat: LOAD DATA LOCAL INFILE runs directly against the connection, never through the caller’s MySqlTransaction — a rolled-back transaction will not undo an already-loaded BulkInsert.

RepoDb.MySqlConnector.BulkOperations (v1.0.0) targets MySqlConnectorConnection, and adds MySqlConnectorBulkImportIdentityBehavior (Unspecified/KeepIdentity/ReturnIdentity). Its bulk-load step is also agnostic of the caller’s transaction — request ReturnIdentity to force the transactional array-bind path if that matters to you.

Neither package has been exercised against a live MySQL instance yet. Verify the bulk-load path, identity read-back, and staging-table strategy in your own environment before relying on it in production.

💥 Breaking changes

With all the good news of the above, we have to introduce the following breaking changes as we invest more in the future.

We did our best to slim the breaking changes as possible, but some of them could not be deprecated due to the collision of the method names, arguments and many others.

We hope that you are able to adapt to the new versions!

💥 Core (RepoDb v1.16.0)

Automatic type-conversion logic in Converter.ToType() has been reworked. A null/DBNull scalar result now raises a clear InvalidCastException naming the offending value and target type — unless GlobalConfiguration.Options.ConversionType is Automatic, the target is a reference or by-ref type, or the target is System.Object. [Exists](https://repodb.net/operation/exists)/ExistsAsync always forces automatic conversion, so they still return false on no match regardless of the global setting. This touches every sync and async overload of [ExecuteScalar](https://repodb.net/operation/executescalar), [Average](https://repodb.net/operation/average), [AverageAll](https://repodb.net/operation/averageall), [Count](https://repodb.net/operation/count), [CountAll](https://repodb.net/operation/countall), [Exists](https://repodb.net/operation/exists), [Max](https://repodb.net/operation/max), [MaxAll](https://repodb.net/operation/maxall), [Min](https://repodb.net/operation/min), [MinAll](https://repodb.net/operation/minall), and [Sum](https://repodb.net/operation/sum).

The where argument is now required — no more default null — on Average, BatchQuery, Count, Max, Min, SkipQuery, and Sum, across BaseRepository, DbRepository, and DbConnection extensions (#1266).

BaseDbSetting.AverageableType is now deprecated.

💥 RepoDb.MySql / RepoDb.MySqlConnector

The obsolete MySqlBootstrap.Initialize() method has been removed. Use GlobalConfiguration.Setup().UseMySql() instead.

💥 RepoDb.PostgreSql

The obsolete PostgreSqlBootstrap.Initialize() method has been removed. Use GlobalConfiguration.Setup().UsePostgreSql() instead.

RepoDb.PostgreSql.BulkOperations renamed several bulk-import types and methods: NpgsqlBulkInsertMapItem becomes PostgreSqlBulkInsertMapItem, BulkImportIdentityBehavior becomes PostgreSqlBulkImportIdentityBehavior, BulkImportMergeCommandType becomes PostgreSqlBulkImportMergeCommandType, BulkImportPseudoTableType becomes PostgreSqlBulkImportPseudoTableType, and BinaryImport/BinaryBulkInsert/BinaryBulkMerge/BinaryBulkDelete/BinaryBulkDeleteByKey become BulkInsert/BulkMerge/BulkDelete/BulkDeleteByKey. The old names aren’t removed — they’re deprecated and remain usable as aliases, so existing code keeps compiling while you migrate.

💥 RepoDb.SqlServer.BulkOperations

isReturnIdentity is gone from BulkInsert and BulkMerge, replaced by an identityBehavior argument typed as SqlServerBulkImportIdentityBehavior. Callers passing a bool need to switch to SqlServerBulkImportIdentityBehavior.ReturnIdentity or .Unspecified.

usePhysicalPseudoTempTable is gone too, replaced by a pseudoTableType argument typed as SqlServerBulkImportPseudoTableType, across BulkInsert, BulkMerge, BulkUpdate, and BulkDelete. Callers passing a bool need to switch to SqlServerBulkImportPseudoTableType.Physical or .Temporary.

The primaryKeys-based overload of BulkDelete has been split out into its own BulkDeleteByKey method. Existing calls to BulkDelete with a list of primary keys need to move to BulkDeleteByKey instead.

On the non-breaking side, a new SqlServerBulkInsertMapItem class brings the same column-mapping API already used by PostgreSQL and Oracle to SQL Server — the base BulkInsertMapItem class still works if you’re already using it.

🟡 Cross-cutting, non-breaking: new IDbSetting extensibility

None of the following break existing code, but they’re the plumbing that made most of the providers above possible. Six new extensibility points on IDbSetting, added in Core:

  • RequiresDbTypeBeforeValue — assigns DbType before Value on a parameter, which Vertica requires.
  • SkipsUnreferencedParameters — skips a bound parameter that has no placeholder in the generated SQL, since a strict provider would otherwise reject the whole command.
  • MaxParameterCount — caps parameters per generated command, batching large IN (…) lists. It defaults to 2,098, lowered to 1,500 for Firebird and Vertica.
  • MultiStatementSeparator — customizes the separator QueryMultiple/QueryMultipleAsync use between statements.
  • SqlTextParameterPrefix — sets the parameter prefix used in raw/text SQL, distinct from the prefix used on bound DbParameters.
  • IsTransactionSupported — declares whether the underlying driver supports transaction objects at all.

Without these, each provider-specific quirk above would have needed a hack in the core statement builder. With them, quirks like Vertica’s parameter-binding order or Firebird’s lower parameter cap are just configuration.

⚠️ Limitations identified

A few limitations apply across every provider — composite keys, auto-generated primary columns, computed columns, JOIN queries, cache invalidation, advanced query-tree expressions, multiple identity columns, and SQL Server Bulk-specific caveats among them. Full detail lives in limitations.md.

🏢 Enterprise readiness

This release also formalizes what RepoDB commits to as organizations evaluate it for production use.

Benchmarks. Independent, reproducible BenchmarkDotNet-based benchmarks compare RepoDB against Dapper, Entity Framework Core, Linq2Db, and NHibernate across every supported provider — CRUD, Batch, and Bulk categories, at 10/100/1000-row scales. They live in src/Benchmarks and are fully open, so clone and run them against your own infrastructure rather than trusting numbers from the project’s CI environment. All nine new providers already have a dedicated benchmark project.

Security. RepoDB’s Security Policy is upfront that this is a single-maintainer project, not a vendor with an SLA. Only the latest published release of each package receives security fixes — there’s no backport policy. Vulnerabilities are reported privately by email, with no public issue tracker and no bug bounty program. Documented risk areas include raw-SQL execution (always parameterize), no credential storage or logging, reflection-based access to non-public provider-driver internals, and no automated dependency-vulnerability scanning across third-party drivers.

Packages. The full package matrix — Core, every provider package, every Bulk-operations add-on, and Telemetry — with live NuGet version and download badges plus per-package CI status, is maintained in PACKAGES.md. It now lists 14 provider packages and 14 matching Bulk-operations packages, up from 5 of each before this release.

Contributing. CONTRIBUTING.md covers how to get involved, including code via “for grabs” issues, bug reports, proposals, and documentation. Every new source file must now carry a copyright header; existing files being modified, not created, keep their existing attribution as-is, since GitHub’s own history already tracks later contributions once a header is applied. RepoDB stays licensed under Apache-2.0, free and open source.

A few honest caveats. There’s no formal versioning or breaking-change policy beyond “only the latest release gets fixes” — pin exact versions and read release notes before upgrading. There’s no formal governance yet, which means single-maintainer bus-factor risk is something to weigh in your own adoption planning. Support follows a documented Support Policy, not an enterprise SLA.

🚀 Where to start

If you’re adopting one of the nine new providers, start with its own package README and its section in limitations.md before writing production code against it — most are early versions and not so mature today, and the caveats above are exactly the ones that will bite first. If you’re upgrading an existing SQL Server, PostgreSQL, MySQL, or SQLite project, read the breaking-changes section closely: the type-conversion rework and the now-required where arguments are the two most likely to need a code change.


~ Conceptualized by me. Reviewed and checked by AI. Refined and gatekept by me. ~

Please support us

Give a ⭐ to our Github repository | Follow me at X / Twitter | Connect with us at our official Teams Channel
Apache License 2.0 — Copyright © 2026 by Michael Camara Pendon / Create a Request