Database migrations
The SaaS template uses MongoDB as its primary database. Connection strings live in each client app’s appsettings files; see the template README for defaults and setup.
Why schema changes matter in MongoDB
MongoDB does not force a rigid schema at write time, but your application does: C# entities, serializers, and queries assume certain field names, shapes, and types. If the database already contains documents and you change the model without updating stored data, you can see:
- Deserialization errors when reading documents that no longer match the type the app expects.
- Missing or null properties where the code assumed a value would exist.
- Silent mismatches (for example an old field name still in the database while the app reads a new name), which show up as broken features or bad data rather than a clear database exception.
So “schema” here means the contract between your BSON documents and your domain model. Migrations are how you evolve that contract safely on existing deployments.
What the DevKit provides (conceptually)
CodeBlock DevKit is delivered as NuGet packages. It includes a migration interface and a runner that:
- Checks whether a migration’s unique name was already applied (using a small history store in the database).
- If not, runs your migration’s
Up()logic against your collections. - Records the migration so it does not run again on the next startup.
You implement a migration class that implements the DevKit migration interface (IDbMigration), give it a stable UniqueName, a human-readable Description, and put your MongoDB update logic in Up(). Then register your class in the template’s DbMigrator so the runner can execute it in the right order alongside any existing migrations.
IDbMigration
The runner’s contract is small; the full interface is:
- Namespace:
CodeBlock.DevKit.Infrastructure.Database.Migrations
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev
namespace CodeBlock.DevKit.Infrastructure.Database.Migrations;
public interface IDbMigration
{
string UniqueName { get; } // e.g., "20240531150000_AddNewFieldToUserEntity"
string Description { get; }
void Up();
}
The runner implementation (IDbMigrationRunner and related types) lives in the same folder in DevKit but is not required reading when you only implement IDbMigration.
Example in the SaaS template
The template ships with a sample migration that renames a BSON field so existing DemoThings documents stay compatible with the domain type (which uses Description instead of Summary). The class implements IDbMigration and uses the driver’s UpdateMany with a rename operation:
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev
using CanBeYours.Core.Domain.DemoThings;
using CanBeYours.Infrastructure.DbContext;
using CodeBlock.DevKit.Infrastructure.Database.Migrations;
using MongoDB.Bson;
using MongoDB.Driver;
namespace CanBeYours.Infrastructure.DbMigrations;
/// <summary>
/// Database migration that renames the 'Summary' field to 'Description' in the DemoThings collection.
/// This class demonstrates how to implement database migrations for schema evolution,
/// showing how to safely update existing data structures without data loss.
///
/// IMPORTANT: This is an example implementation for learning purposes. Replace this migration
/// with your actual business domain schema changes and migrations.
///
/// Key features demonstrated:
/// - Database migration pattern implementation
/// - Safe field renaming using MongoDB operations
/// - Migration versioning and identification
/// - Database context integration
/// </summary>
internal class RenameSummaryToDescription : IDbMigration
{
private readonly MainDbContext _dbContext;
/// <summary>
/// Initializes a new instance of the migration with the database context.
/// </summary>
/// <param name="dbContext">The database context for accessing collections</param>
public RenameSummaryToDescription(MainDbContext dbContext)
{
_dbContext = dbContext;
}
/// <summary>
/// Unique identifier for this migration to prevent duplicate execution.
/// This property ensures that the migration runs only once and can be
/// tracked in migration history.
/// </summary>
public string UniqueName => "1_Rename_Summary_To_Description";
/// <summary>
/// Human-readable description of what this migration accomplishes.
/// This description helps developers understand the purpose and scope
/// of the migration during deployment and troubleshooting.
/// </summary>
public string Description => "Renames the 'Summary' property to 'Description' in the DemoThings collection.";
/// <summary>
/// Executes the migration to rename the 'Summary' field to 'Description'.
/// This method demonstrates how to perform safe schema changes on existing
/// MongoDB collections using the UpdateMany operation.
///
/// The migration:
/// 1. Finds all documents that have a 'Summary' field
/// 2. Renames the field to 'Description' without losing data
/// 3. Uses BsonDocument operations for field-level changes
/// </summary>
public void Up()
{
var settings = _dbContext.GetCollection(nameof(MainDbContext.DemoThings));
var filter = Builders<BsonDocument>.Filter.Exists("Summary");
var update = Builders<BsonDocument>.Update.Rename("Summary", nameof(DemoThing.Description));
settings.UpdateMany(filter, update);
}
}
Migrations are registered in order in DbMigrator.cs. The extension method builds a list of IDbMigration instances and passes each one to the DevKit’s migration runner:
using CanBeYours.Infrastructure.DbMigrations;
using CodeBlock.DevKit.Infrastructure.Database.Migrations;
using Microsoft.Extensions.DependencyInjection;
namespace CanBeYours.Infrastructure.DbContext;
/// <summary>
/// Database migration runner that applies database schema changes in order.
/// This class demonstrates how to manage database schema evolution through
/// versioned migrations that can be applied safely to production databases.
///
/// IMPORTANT: This is an example implementation for learning purposes. Replace the
/// DemoThing migrations with your actual business domain schema migrations.
///
/// Key features demonstrated:
/// - Ordered migration execution
/// - Migration dependency management
/// - Safe database schema updates
/// - Service scope management for migrations
/// </summary>
internal static class DbMigrator
{
/// <summary>
/// Applies all pending database migrations in the correct order.
/// This method ensures that database schema changes are applied safely
/// and in the proper sequence during application startup.
///
/// Example usage in startup:
/// serviceProvider.MigrateDatabes();
/// </summary>
/// <param name="serviceProvider">The service provider to access required services</param>
public static void MigrateDatabes(this IServiceProvider serviceProvider)
{
using var serviceScope = serviceProvider.CreateScope();
var dbContext = serviceScope.ServiceProvider.GetService<MainDbContext>();
var dbMigrationRunner = serviceScope.ServiceProvider.GetService<IDbMigrationRunner>();
var migrations = new List<IDbMigration>
{
new RenameSummaryToDescription(dbContext),
// Add more migrations here as needed
};
foreach (var migration in migrations)
{
dbMigrationRunner.ApplyMigration(migration);
}
}
}
During infrastructure startup, the template invokes that extension so pending migrations run when the app starts — see Startup.cs (serviceProvider.MigrateDatabes();).
When you add your own changes, follow the same pattern: implement IDbMigration, append your migration to the list in DbMigrator after any existing entries so order stays deterministic, and choose a globally unique UniqueName per migration (the sample uses a prefixed style such as 1_Rename_Summary_To_Description).