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.