Switches the database from SQL Server to MariaDB
The target Pi only has MariaDB, and SQL Server has no ARM64 build at all - not a config problem, a real gap discovered during deployment setup. Swapped the EF Core provider, regenerated every migration, updated connection strings and the backup script everywhere they appear. Took two tries to land on a provider that actually works: Pomelo builds fine against this project's EF Core 10 packages but fails at runtime (it's compiled against 9's internal API surface, which moved in 10 wherever Identity/DataProtection force the newer packages). Oracle's official provider builds and migrates fine but has a real MariaDB bug in its own migration-lock code, reproduced against a live database. Kept Oracle's provider and worked around just that one broken method - everything else it does is correct - rather than give up more of the stack to chase a workaround. Verified against a real local MariaDB end to end: all three migrations applied, both hosts start clean, full suite still green.
This commit is contained in:
@@ -57,6 +57,72 @@
|
||||
- **Artifacts**: `operations/deployment/deployment-plan.md`, `deployment-instructions.md`,
|
||||
`rollback-plan.md`
|
||||
|
||||
### Database Provider Migration — SQL Server → MariaDB (2026-07-29)
|
||||
|
||||
Discovered while finalizing Deployment Setup: the target Pi only runs MariaDB, and Microsoft SQL
|
||||
Server has **no ARM64 build at all** (the `mcr.microsoft.com/mssql/server` image is `linux/amd64`
|
||||
only; Azure SQL Edge, the former ARM answer, is retired). This invalidates ASM-04. Confirmed no
|
||||
other machine is available, and the eventual production host (`mijnhostingpartner.nl`) will also
|
||||
run MariaDB, plus the workload is small enough that MariaDB's performance is not a concern — user
|
||||
decided: switch the database provider, not the deployment target.
|
||||
|
||||
**What changed** (application code, not just Operations docs):
|
||||
- `SlpModularCms.Core.csproj`: `Microsoft.EntityFrameworkCore.SqlServer` → `MySql.EntityFrameworkCore`
|
||||
10.0.7 (Oracle's official provider)
|
||||
- `UseSqlServer(...)` → `UseMySQL(...)` in `ServiceCollectionExtensions.cs`, `AvailabilityModule.cs`,
|
||||
`MasterModule.cs`
|
||||
- `DatabaseMigrationExtensions.cs` + its test: `Microsoft.Data.SqlClient.SqlException` →
|
||||
`MySql.Data.MySqlClient.MySqlException` for the transient-failure classifier
|
||||
- All three DbContexts' migrations deleted and regenerated (`ApplicationDbContext`,
|
||||
`AvailabilityDbContext`, `MasterDbContext`) — no MySQL/MariaDB model-compatibility issues surfaced
|
||||
(no index-length problems, no raw SQL anywhere in the codebase to translate)
|
||||
- Connection strings updated across `appsettings.json` (Api + Api.Slave, all three tiers) from
|
||||
SQL Server format to MySQL format
|
||||
- `README.md` dev setup: MariaDB container command replacing the SQL Server one, with a migration
|
||||
note for anyone returning to old instructions
|
||||
- `deployment-instructions.md` / `rollback-plan.md`: connection string format, backup script
|
||||
rewritten around `mariadb-dump` (was `sqlcmd`/`BACKUP DATABASE`), restore procedure rewritten
|
||||
|
||||
**Provider selection — two failed attempts before the working one, both reproduced against a real
|
||||
MariaDB instance, not just reasoned about:**
|
||||
1. **Pomelo.EntityFrameworkCore.MySql** (the usual first choice, explicit first-class MariaDB
|
||||
support) — caps at EF Core 9.x, no EF Core 10 release exists. A Pomelo maintainer states
|
||||
([PR #2017](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/2017))
|
||||
that Pomelo 9 / EF Core 9 packages work fine on a net10.0 TFM — confirmed true only when nothing
|
||||
else forces EF Core 10 packages. This project's `Microsoft.AspNetCore.Identity.EntityFrameworkCore`
|
||||
and `Microsoft.AspNetCore.DataProtection.EntityFrameworkCore` are versioned in lockstep with the
|
||||
.NET 10 runtime and hard-require EF Core >= 10.0.9, so the resolved `Microsoft.EntityFrameworkCore.Abstractions`
|
||||
ends up at 10.0.9 regardless. Restore only warns (NU1608), and it builds — but fails at runtime
|
||||
with `MissingMethodException: AbstractionsStrings.ArgumentIsEmpty` the moment EF tooling touches
|
||||
a DbContext: Pomelo's compiled assembly calls an internal EF Core 9 helper that no longer exists
|
||||
in the 10.0.9 assembly actually loaded.
|
||||
2. **MySql.EntityFrameworkCore 10.0.7** (Oracle's official provider) — its net10.0 dependency group
|
||||
targets EF Core 10.0.7, compatible with 10.0.9. Builds and migrates cleanly, but
|
||||
`dotnet ef database update` against the real MariaDB throws
|
||||
`InvalidCastException: Unable to cast object of type 'System.DBNull' to type 'System.Int64'` in
|
||||
`MySQLHistoryRepository.AcquireDatabaseLock()` — a confirmed MariaDB-incompatibility bug (MariaDB's
|
||||
`GET_LOCK()` apparently returns `NULL` in a case Oracle's code doesn't handle, and Oracle's
|
||||
provider is tested against real MySQL Server, not MariaDB). This isn't limited to CLI tooling —
|
||||
the same code path runs on every application startup via `MigrateCoreDatabase()`.
|
||||
|
||||
**Working solution**: kept Oracle's `MySql.EntityFrameworkCore` 10.0.7 (otherwise fully compatible)
|
||||
and added `NonLockingMySQLHistoryRepository` (`SlpModularCms.Core/Hosting/`), wired in via
|
||||
`options.ReplaceService<IHistoryRepository, NonLockingMySQLHistoryRepository>()` on all three
|
||||
`AddDbContext` registrations. Oracle's internal `MySQLHistoryRepository` class can't be subclassed
|
||||
(it's `internal`, only its constructor is public), so the workaround constructs a real instance of
|
||||
it via reflection and forwards every `IHistoryRepository` member to it **except**
|
||||
`AcquireDatabaseLock`/`AcquireDatabaseLockAsync`, which return a no-op lock instead of ever reaching
|
||||
the broken `GET_LOCK` call. Accepted as safe because this deployment model never runs migrations
|
||||
from more than one place at a time (`MigrateCoreDatabase()` at startup, one deploy at a time via the
|
||||
atomic-release sequence) — a genuinely concurrent multi-instance migration race is not a scenario
|
||||
this architecture produces.
|
||||
|
||||
**Verified end to end**: all three migrations applied successfully to the user's real local
|
||||
MariaDB (`dotnet ef database update`, all expected tables present including `DataProtectionKeys`
|
||||
and the renamed Identity tables); both `SlpModularCms.Api` and `SlpModularCms.Api.Slave` start
|
||||
cleanly against it (`/health` → 200, `MigrateCoreDatabase()` logs "already up to date" on the
|
||||
second run); full backend suite re-confirmed at 372/372 passed, 0 build errors.
|
||||
|
||||
## Scope Decisions (from feature-selection.md)
|
||||
- **Public website**: documentation/instructions only — where the website build lands in `wwwroot/`, how it coexists with `wwwroot/admin/`, and what a per-website workspace must deliver. The website's own build/deploy workflow stays out of scope (Q4 = A).
|
||||
- **Environments**: local, test, production only.
|
||||
|
||||
Reference in New Issue
Block a user