EF Core con MariaDB via Pomelo: gli errori più comuni e come evitarli

EF Core con MariaDB via Pomelo: gli errori più comuni e come evitarli

Il problema più comune con Entity Framework Core e MariaDB non è la connessione al database, ma il disallineamento di versione tra .NET/EF Core e il pacchetto Pomelo.EntityFrameworkCore.MySql, che spesso non ha ancora supporto stabile per l'ultima major di EF Core al momento in cui esce. Il secondo problema più comune riguarda le alternate key su colonne AUTO_INCREMENT, che richiedono HasAlternateKey() invece del più intuitivo HasIndex().IsUnique() — un errore che passa inosservato in sviluppo e si manifesta solo quando MariaDB rifiuta lo schema generato.

Perché serve Pomelo e non il provider MySQL ufficiale

EF Core non include un provider nativo per MySQL/MariaDB — Microsoft mantiene provider ufficiali solo per SQL Server, Cosmos DB, SQLite e (in-memory) per i test. Pomelo.EntityFrameworkCore.MySql è il provider open source più maturo, compatibile sia con MySQL sia con MariaDB (con qualche differenza sotto il cofano che vale la pena conoscere).

Il punto critico: Pomelo segue le versioni di EF Core, ma non sempre pubblica supporto stabile per una nuova major .NET esattamente al lancio. Prima di aggiornare .NET/EF Core in un progetto che usa Pomelo, la domanda da farsi non è "l'ultima versione di .NET è uscita?" ma "Pomelo ha già una release stabile compatibile?" — sono due tempi diversi, e ignorarlo porta a un progetto bloccato tra un dotnet restore che non risolve le dipendenze e un downgrade forzato dell'intero stack in un secondo momento.

Alternate key su colonne AUTO_INCREMENT

Questo è l'errore più insidioso perché compila senza problemi e spesso funziona anche in sviluppo, salvo fallire silenziosamente o generare uno schema inconsistente in scenari specifici. Il pattern sbagliato, tentato istintivamente da chi viene da SQL Server:

// Sbagliato con MariaDB su una colonna AUTO_INCREMENT
modelBuilder.Entity<Technology>()
    .HasIndex(t => t.Slug)
    .IsUnique();

HasIndex().IsUnique() crea un indice univoco, non un vincolo di chiave alternativa — per MariaDB, quando la colonna coinvolta è (o è collegata a) una colonna AUTO_INCREMENT, questa distinzione conta. Il pattern corretto:

modelBuilder.Entity<Technology>()
    .HasAlternateKey(t => t.Slug);

HasAlternateKey() dice esplicitamente a EF Core "questa è una chiave alternativa", generando il vincolo che MariaDB si aspetta in combinazione con l'auto-increment sulla chiave primaria. La differenza sembra sottile finché non si guarda cosa succede a runtime: con HasIndex().IsUnique() in certi scenari di relazione (una foreign key che punta a quella colonna invece che alla PK) EF Core genererà una migration che MariaDB rifiuta o applica in modo inconsistente rispetto a quanto ci si aspetta.

Migration da Package Manager Console: startup project e default project contano

Un errore frequente in team con più sviluppatori: lanciare Add-Migration con la combinazione sbagliata di progetti selezionati in Visual Studio. Con un'architettura a progetti separati (entità/DbContext in un progetto, API in un altro), servono entrambi i riferimenti impostati correttamente:

Se questi due non sono allineati, il sintomo tipico è Unable to create a 'DbContext' of type... oppure — più insidioso — la migration si genera ma finisce nel progetto sbagliato, silenziosamente, se il default project era quello giusto ma lo startup project no (in quel caso spesso l'errore è invece "no connection string configured", più immediato da diagnosticare).

Un dettaglio da non sottovalutare: Microsoft.EntityFrameworkCore.Design deve essere installato nel progetto di startup, non in quello con le entità — è un errore comune installarlo nel progetto Infrastructure/Core pensando sia lì che serva, quando invece è il progetto eseguibile a doverne avere bisogno per gli strumenti da riga di comando/PMC.

Se la corruzione della cache MSBuild è sospetta (sintomi tipici: la migration si genera ma referenzia un modello vecchio, o Visual Studio "non vede" un'entità appena aggiunta), la soluzione più affidabile resta cancellare tutte le cartelle bin/obj della solution prima di riprovare — spesso più veloce che diagnosticare la causa esatta della cache incoerente.

Nessun SDK .NET sul server di produzione: come si applicano le migration

Se il server di produzione ha solo il runtime ASP.NET Core (non l'SDK completo), dotnet ef database update non è un'opzione lì. Il flusso corretto è generare lo script SQL in locale (dove l'SDK c'è) e applicarlo manualmente:

dotnet ef migrations script --idempotent -o migrations.sql

Il flag --idempotent è importante: genera uno script che controlla quali migration sono già state applicate (leggendo __EFMigrationsHistory) prima di eseguire ciascuna — questo permette di rieseguire lo stesso script più volte senza rischio di duplicare comandi già applicati, utile quando si aggiorna un ambiente di cui non si è sicuri al 100% dello stato esatto. Lo script va poi trasferito ed eseguito con il client mysql/mariadb da riga di comando, non con strumenti .NET.

Domande frequenti

Posso usare il provider MySQL ufficiale di Oracle invece di Pomelo? Esiste MySql.EntityFrameworkCore di Oracle/MySQL, ma il supporto per MariaDB specificamente (che ha divergenze crescenti da MySQL nelle versioni più recenti) è storicamente meno curato rispetto a Pomelo, che è nato e si mantiene con MariaDB come caso d'uso di prima classe, non secondario.

HasAlternateKey() va usato per ogni colonna con vincolo di unicità? No — solo quando quella colonna è coinvolta in una relazione (foreign key che la referenzia) e nel contesto MariaDB/auto-increment descritto sopra. Per un semplice vincolo di unicità senza relazioni collegate, HasIndex().IsUnique() resta corretto e sufficiente.

Perché la migration generata da EF Core a volte include modifiche che non ho fatto io? Quasi sempre perché il modello effettivo nel database (o l'ultima migration applicata) è disallineato con lo snapshot che EF Core tiene internamente (ModelSnapshot) — capita dopo modifiche fatte a mano al database o migration cancellate manualmente senza rigenerare lo snapshot. Vale la pena controllare il diff della migration generata riga per riga prima di applicarla, non fidarsi ciecamente del comando.

Il downgrade di .NET per compatibilità con Pomelo è una soluzione temporanea o va tenuto a lungo? Dipende dalla roadmap di Pomelo per quella specifica major — vale la pena tenere sotto controllo le loro release invece di ripetere il tentativo di aggiornamento a intervalli casuali; quando pubblicano supporto stabile, l'aggiornamento va comunque testato a fondo prima di portarlo in produzione, non applicato al volo.