Talvolta nasce la necessità di comunicare con diversi provider di database mantenendo lo stesso codice, al fine di soddisfare, per esempio, le esigenze di un cliente nell’installazione di un’applicazione on-premise. Tuttavia, non tutte le funzioni sono ugualmente supportate da tutti i database e la sintassi SQL può variare da un provider all’altro.
Riportando un esempio:
SQL Server
SELECT TOP(10) * FROM Clienti
MySQL
SELECT * FROM Clienti LIMIT 10
È necessario separare la logica delle query dalla sintassi SQL in modo che un intermediario possa fare da “traduttore” e generare l’SQL corretto per il provider specifico.
1. Entity Framework Core 6 : l’ORM di Microsoft per .NET
L’Object-Relational Mapping (ORM) è un concetto che si dimostra essere particolarmente utile in questo contesto. Si tratta di una libreria che permette di astrarre la persistenza e lavorare con oggetti C#, generando automaticamente l’SQL necessario per interagire con il database in base al provider configurato. In questo modo, è possibile lavorare con operazioni sugli oggetti pensando in linguaggio C#, mentre il DbContext (ovvero la classe che rappresenta il database in EF Core) genera l’SQL per l’interazione effettiva con il database. Le query vengono scritte in LINQ, una sintassi che consente di scrivere query complesse in C#.
2. Adattatori per provider di database
3. Come configurare un provider?
Ogni DbContext (rappresentante un database) può essere associato al proprio provider di database, consentendo l’interazione “parallela” con due provider di database diversi.
Di seguito un esempio di configurazione per provider MySql:
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
string connstring = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ProgramDbContext>(
c => c.UseMySql(connstring, ServerVersion.AutoDetect(connstring), opt => opt.MigrationsAssembly("Esempio.MultiDb.Common")));
Configurazione Sql Server:
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
string connstring = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ProgramDbContext>(
c => c.UseSqlServer(connstring,b => b.MigrationsAssembly("Esempio.MultiDb.Common));
4. Quali sono i problemi più comuni?
-
Funzioni built-in specifiche di un provider
Spesso i provider di database offrono funzioni integrate per operazioni comuni come ottenere una parte di una data o calcolare la differenza tra due date. È possibile accedere a queste funzioni durante le query LINQ utilizzando la classe statica EF.Functions. Tuttavia, non tutti i provider supportano tutte le funzioni, quindi è sempre meglio verificare la compatibilità della funzione desiderata. Se la funzione non è supportata da tutti i provider, è possibile eseguire il calcolo a livello applicativo, anche se potrebbe essere meno efficiente.
-
Strutture e oggetti del database
Entity Framework Core consente di definire indici, vincoli, viste e chiavi nello schema del database. Se si desidera utilizzare un singolo DbContext, è necessario assicurarsi che le strutture siano supportate da tutti i provider, altrimenti si verificherà un errore durante la creazione delle migrazioni o l'applicazione delle stesse.
-
Migrazioni
Entity Framework Core gestisce le operazioni sulla struttura del database attraverso le migrazioni, che sono pacchetti di codice incrementali che eseguono SQL per allineare il database alla struttura definita nel codice. Poiché l'SQL per la creazione di alcune strutture può variare da provider a provider, è necessario generare migrazioni specifiche per ogni provider gestito. Si noti che questo problema si presenta solo se si desidera utilizzare lo stesso DbContext per due provider diversi. Se si desidera gestire due provider diversi utilizzando due DbContext separati, le migrazioni terranno già conto del provider specifico per ogni DbContext.
Conclusione
Grazie ad Entity Framework Core, è possibile cambiare il tipo di provider in base alle esigenze del cliente e utilizzare la stessa logica applicativa con database diversi, sebbene con alcune limitazioni.