Rystem.RepositoryFramework.MigrationTools 10.0.8

dotnet add package Rystem.RepositoryFramework.MigrationTools --version 10.0.8
                    
NuGet\Install-Package Rystem.RepositoryFramework.MigrationTools -Version 10.0.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Rystem.RepositoryFramework.MigrationTools" Version="10.0.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Rystem.RepositoryFramework.MigrationTools" Version="10.0.8" />
                    
Directory.Packages.props
<PackageReference Include="Rystem.RepositoryFramework.MigrationTools" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Rystem.RepositoryFramework.MigrationTools --version 10.0.8
                    
#r "nuget: Rystem.RepositoryFramework.MigrationTools, 10.0.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Rystem.RepositoryFramework.MigrationTools@10.0.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Rystem.RepositoryFramework.MigrationTools&version=10.0.8
                    
Install as a Cake Addin
#tool nuget:?package=Rystem.RepositoryFramework.MigrationTools&version=10.0.8
                    
Install as a Cake Tool

Rystem.RepositoryFramework.MigrationTools

Rystem.RepositoryFramework.MigrationTools adds IMigrationManager<T, TKey> for moving data from one Repository Framework registration to another.

It is designed around named registrations: one source factory and one destination factory. The source must be readable, the destination must be writable.

Installation

dotnet add package Rystem.RepositoryFramework.MigrationTools

Architecture

The migration manager resolves services through Repository Framework factories.

  • source: IQuery<T, TKey> when available, otherwise IRepository<T, TKey>
  • destination: ICommand<T, TKey> when available, otherwise IRepository<T, TKey>
  • destination-as-repository: optional, used only for existence checks and destination cleanup

This lets you migrate across:

  • repository to repository
  • query to command
  • query to repository
  • repository to command

as long as the named registrations exist.

Basic example

builder.Services
    .AddRepository<User, string>(repositoryBuilder =>
    {
        repositoryBuilder.WithInMemory(builder =>
        {
            builder.PopulateWithRandomData(1000);
        }, name: "source");
    })
    .AddRepository<User, string>(repositoryBuilder =>
    {
        repositoryBuilder.WithInMemory(name: "target");
    })
    .AddMigrationManager<User, string>(options =>
    {
        options.SourceFactoryName = "source";
        options.DestinationFactoryName = "target";
        options.NumberOfConcurrentInserts = 10;
    });

var serviceProvider = builder.Services.Finalize();
await serviceProvider.WarmUpAsync();

Then inject the manager and run the migration:

public sealed class MigrationRunner(IMigrationManager<User, string> migrationManager)
{
    public Task<bool> RunAsync(CancellationToken cancellationToken)
        => migrationManager.MigrateAsync(x => x.Id!, cancellationToken: cancellationToken);
}

Registering IMigrationManager<T, TKey>

Use AddMigrationManager<T, TKey>(...).

builder.Services.AddMigrationManager<User, string>(options =>
{
    options.SourceFactoryName = "source";
    options.DestinationFactoryName = "target";
    options.NumberOfConcurrentInserts = 20;
});

MigrationOptions<T, TKey>

Property Default Notes
SourceFactoryName required Must match a named IQuery<T, TKey> or IRepository<T, TKey> registration.
DestinationFactoryName required Must match a named ICommand<T, TKey> or IRepository<T, TKey> registration.
NumberOfConcurrentInserts 10 Upper bound used by the migration loop before it waits for the current batch of insert tasks.

Startup validation performed by AddMigrationManager(...):

  • SourceFactoryName and DestinationFactoryName are normalized to empty strings when null
  • source and destination names must be different, otherwise registration throws

MigrateAsync(...)

Task<bool> MigrateAsync(
    Expression<Func<T, TKey>> navigationKey,
    bool checkIfExists = false,
    bool deleteEverythingBeforeStart = false,
    CancellationToken cancellationToken = default)

What the current implementation does

  1. resolve the configured source and destination factories
  2. optionally clear the destination first
  3. read the entire source using QueryAsync(IFilterExpression.Empty)
  4. materialize the full result set into memory with ToListAsync()
  5. insert entities into the destination in batches of concurrent tasks

This package is therefore a straightforward bulk copy helper, not a streaming migration pipeline.

Source and destination requirements

Scenario Source requirement Destination requirement
basic migration IQuery<T, TKey> or IRepository<T, TKey> ICommand<T, TKey> or IRepository<T, TKey>
checkIfExists = true same as above IRepository<T, TKey>
deleteEverythingBeforeStart = true same as above IRepository<T, TKey>

When a requirement is not met, the manager throws an ArgumentException with a descriptive message.

Examples by scenario

Migrate from one named repository to another

builder.Services
    .AddRepository<Order, long>(repositoryBuilder =>
    {
        repositoryBuilder.WithInMemory(builder =>
        {
            builder.PopulateWithRandomData(500);
        }, name: "old");
    })
    .AddRepository<Order, long>(repositoryBuilder =>
    {
        repositoryBuilder.WithInMemory(name: "new");
    })
    .AddMigrationManager<Order, long>(options =>
    {
        options.SourceFactoryName = "old";
        options.DestinationFactoryName = "new";
        options.NumberOfConcurrentInserts = 25;
    });

Migrate between different storage backends

builder.Services
    .AddRepository<Product, Guid>(repositoryBuilder =>
    {
        repositoryBuilder.WithEntityFramework<AppDbContext>(x => x.Products, name: "sql");
    })
    .AddRepository<Product, Guid>(repositoryBuilder =>
    {
        repositoryBuilder.WithBlobStorage(storage =>
        {
            storage.Settings.ConnectionString = builder.Configuration["ConnectionStrings:Storage"];
        }, name: "blob");
    })
    .AddMigrationManager<Product, Guid>(options =>
    {
        options.SourceFactoryName = "sql";
        options.DestinationFactoryName = "blob";
        options.NumberOfConcurrentInserts = 20;
    });

Skip items that already exist

await migrationManager.MigrateAsync(
    navigationKey: x => x.Id,
    checkIfExists: true,
    cancellationToken: cancellationToken);

When checkIfExists is enabled, the manager calls ExistAsync(entity.Key!) on the destination repository before inserting.

Clear destination before migrating

await migrationManager.MigrateAsync(
    navigationKey: x => x.Id,
    deleteEverythingBeforeStart: true,
    cancellationToken: cancellationToken);

When deleteEverythingBeforeStart is enabled, the manager loads the destination with ToListAsync() and deletes items one by one with DeleteAsync(...) before starting inserts.

Important behavior notes from the source

These details matter when you use the package in production-like migrations.

MigrateAsync(...) accepts Expression<Func<T, TKey>> navigationKey, but the current implementation inserts using entity.Key! from the source query result and never evaluates the expression.

Practical consequence:

  • the migration currently depends on the source repository returning correct Entity<T, TKey>.Key values
  • changing navigationKey does not change the destination key in the current implementation

Source data is fully materialized first

The migration manager executes:

await _source.QueryAsync(IFilterExpression.Empty, cancellationToken: cancellationToken)
    .ToListAsync();

That means:

  • the whole source dataset is loaded before inserts begin
  • memory usage grows with the full migration size
  • there is no built-in pagination or chunked source enumeration

Destination cleanup is sequential

When deleteEverythingBeforeStart is true, destination deletion happens one item at a time.

Final insert batch caveat

The current implementation waits only when the in-flight task count becomes greater than NumberOfConcurrentInserts, and it does not explicitly await the final partial batch before returning true.

For small or non-divisible workloads, that means the method can report completion before the last queued inserts have definitely finished.

If you rely on strict end-of-migration completion semantics, verify the destination after the call or patch the implementation before using it for critical data moves.

Cancellation behavior

The migration loop checks cancellationToken.IsCancellationRequested before queueing each new insert. When cancellation is observed there, the method returns false.

Already-started insert tasks are not rolled back.

Warm-up note

The migration package does not bootstrap repositories for you. If your source or destination uses warm-up driven infrastructure, such as seeded in-memory repositories, run WarmUpAsync() before starting the migration.

When to use this package

Use it when you want:

  • a simple named-source to named-destination migration helper
  • a way to copy data between Repository Framework integrations without writing one-off glue code
  • a migration utility for test environments, storage switches, or one-time backfills

For very large datasets or strict completion guarantees, treat the current package as a starting point and review the implementation in RepositoryFramework.MigrationTools/Managers/MigrationManager.cs before depending on it operationally.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.8 100 5/13/2026
10.0.7 116 3/26/2026
10.0.6 433,482 3/3/2026
10.0.5 114 2/22/2026
10.0.4 121 2/9/2026
10.0.3 147,901 1/28/2026
10.0.1 209,101 11/12/2025
9.1.3 259 9/2/2025
9.1.2 764,469 5/29/2025
9.1.1 97,831 5/2/2025
9.0.32 186,734 4/15/2025
9.0.31 5,794 4/2/2025
9.0.30 88,836 3/26/2025
9.0.29 9,029 3/18/2025
9.0.28 275 3/17/2025
9.0.27 279 3/16/2025
9.0.26 265 3/13/2025
9.0.25 52,132 3/9/2025
9.0.21 339 3/6/2025
9.0.20 19,602 3/6/2025
Loading failed