EF Core migrations
Plugins that persist data (Schulware, OdaOrg) own their schema via EF Core migrations.
Each plugin gets its own database
The backend's plugin host hands the plugin a dedicated connection string via PluginServiceContext.ConnectionString - it mutates the host connection string so the database name becomes schuly_plugin_<name>. The plugin wires it into its DbContext:
services.AddDbContext<SchulwareDbContext>(o => o.UseNpgsql(context.ConnectionString));Migrations therefore never touch the main Schuly database; each plugin's schema is isolated.
Adding a migration
dotnet ef migrations add <Name> --project src/Schuly.Plugin.Schulware(Swap the project path for the plugin you're working on.) This writes the migration into the plugin's Data/Migrations/ folder and updates the model snapshot. Requires the dotnet-ef tool - see setup/development.md.
Design-time factory
dotnet ef constructs the DbContext at design time without the runtime DI pipeline, so each plugin ships an IDesignTimeDbContextFactory<T> next to its DbContext (e.g. Data/SchulwareDbContextFactory.cs). The connection string in the factory is a throwaway placeholder - migrations only need the model, not a live database:
internal sealed class SchulwareDbContextFactory : IDesignTimeDbContextFactory<SchulwareDbContext>
{
public SchulwareDbContext CreateDbContext(string[] args) =>
new(new DbContextOptionsBuilder<SchulwareDbContext>()
.UseNpgsql("Host=localhost;Database=schulware_design;Username=postgres;Password=postgres")
.Options);
}Apply migrations at runtime, not EnsureCreated
Apply pending migrations from MigrateAsync:
public async Task MigrateAsync(IServiceProvider sp, CancellationToken ct = default)
{
using var scope = sp.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<SchulwareDbContext>();
await db.Database.MigrateAsync(ct);
}Use MigrateAsync(), never EnsureCreatedAsync. EnsureCreatedAsync creates the database on first run but does nothing on later schema changes, so column/index additions would never land on an existing database. MigrateAsync() creates the DB on first run and applies every schema delta afterwards.
