Introducing the Schema Mover: The Foundation of RepoDB's Productivity Platform

We are introducing the Schema Mover, a new capability of RepoDB that copies a table’s structure from one database to another. It ships as two kinds of packages:

  • RepoDb.Schema.Core holds the provider-neutral model, the contracts and the CopySchemaTo operation.
  • RepoDb.Schema.<DbProvider> holds one package per database engine, each with a schema reader and a schema composer. RepoDb.Schema.SqlServer is the first.

This is the first building block of RepoDB’s upcoming Productivity Platform, and it comes before anything else in it.

What it does

The Schema Mover reads a table from the source database and converts it to a neutral description: its columns, primary key, indexes, foreign keys, and unique and check constraints. It then generates the DDL for the destination database and runs it there.

Source DB  →  Schema Reader  →  TableSchema (neutral)  →  Schema Composer  →  Destination DB

The reader and the composer are resolved independently from the connection types. Once both engines have a provider, any source can target any destination, for example SQL Server to PostgreSQL. Providers never depend on each other, because the neutral TableSchema is the only thing they share.

Why the Schema Mover comes before the Data Mover

The Productivity Platform’s main feature will be the Data Mover: copying and moving rows between databases, across engines, on-premise or cloud-native. But data can only move into a table that already exists. Without the Schema Mover, every data copy would require developers to hand-write DDL for every destination engine. That is exactly the work the Productivity Platform is meant to remove.

Schema is the harder half of the problem. Type systems differ between engines, identities and sequences behave differently, and indexes and constraints have their own syntax. Foreign keys force tables to be created in dependency order. If the Data Mover were built first, each of those problems would be solved again, partially, inside it.

This is why the Schema Mover is a requirement, not an option. Not a single line of the Data Mover will be written until it is in place. The Data Mover will be built on top of it, the same way the Productivity layer is built on top of the ORM.

Why build it ourselves

Before starting this work, I looked at existing .NET schema and migration libraries. I also spoke with the creators of some of them, including Yuniql and FluentMigrator. Both are well-established projects, and I explored working together on this. These conversations never turned into anything concrete. That isn’t a criticism of either project; each has its own priorities and direction, and they don’t match what RepoDB needs.

RepoDB’s needs are about to grow. The Data Mover must read a live schema from any supported engine and recreate it on any other, on demand, and keep up with every new provider we add. With that much depending on this capability, relying on an outside library whose roadmap we don’t control would put the whole Productivity Platform at risk. Building the Schema Mover inside RepoDB removes that risk. It follows the same conventions, releases on the same schedule, and supports the same providers as the rest of RepoDB.

A simple example

Register the providers once at startup, then copy a table’s schema with a single call:

// Once, at startup
GlobalConfiguration
    .Setup()
    .UseSqlServer();
SqlServerSchemaBootstrap.Initialize();

// Copy the schema of the "Customer" table from the source to the destination
using var source = new SqlConnection(sourceConnectionString);
using var destination = new SqlConnection(destinationConnectionString);

var result = source.CopySchemaTo("Customer", destination);

Console.WriteLine($"{result.Outcome}: {result.ColumnCount} columns, {result.IndexCount} indexes");

The source table is never modified. The call returns a CopySchemaResult with the outcome, the object counts, the generated Script, and any Warnings.

If the table already exists in the destination, the tableExistenceBehavior argument decides what happens:

var result = source.CopySchemaTo("Customer", destination,
    tableExistenceBehavior: CopySchemaExistsBehavior.AlignOnExists);
Behavior What happens when the destination table exists
SkipOnExists (default) Nothing; the table is left as it is.
AlignOnExists The missing columns and indexes are added.
ThrowOnExists An exception is thrown.
DropOnExists The table is dropped and created again. This permanently deletes its data.

Both an async version (CopySchemaToAsync) and an entity-based version (CopySchemaTo<Customer>) are available.

What’s next

  • More providers. PostgreSQL, MySQL and SQLite come next after SQL Server, followed by the rest of RepoDB’s providers.
  • Cross-engine type mapping. A warning is added to Warnings whenever a column type cannot be mapped exactly.
  • Multi-table copies in dependency order. Parent tables are created before the foreign keys that reference them.
  • Then, the Data Mover. Once the Schema Mover is in place, we will build CopyDataTo and MoveDataTo on top of it.

We would love to hear your feedback, especially about the schemas and engine pairs that matter most to you. It directly shapes what we prioritize next.


~ 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