Entity Framework Core

Nel 2016 è stato rilasciato .NET Core, l’evoluzione del framework .NET. La compatibilità cross-platform con sistemi non-Windows ed il miglioramento del modello architetturale hanno reso .NET Core il successore naturale del vecchio framework .NET. Naturalmente anche Entity Framework, alla base del sistema di accesso dati in molte applicazioni web, doveva evolvere di conseguenza: nasce così Entity Framework Core.

Come spesso accade, tuttavia, l’evoluzione non è scevra di insidie. Il framework EF Core è stato riscritto da zero, ed inevitabilmente è ancora carente di molte funzionalità dell’antenato, ampiamente testato e con anni di sviluppo alle spalle. Per alcuni aspetti si dimostra già superiore; per altri, deve ancora raggiungere le potenzialità dell’ultima versione di Entity Framework. Altre funzionalità sono semplicemente diverse, e richiedono un approccio differente da parte del programmatore.

In questo articolo, vedremo alcune delle principali differenze tra i due framework, discutendone pro e contro, in modo da poter affrontare meglio la grande domanda: siete pronti a sviluppare la vostra prossima applicazione con Entity Framework Core?

Non solo cross-platform

Un framework Microsoft che diventa cross-platform è una grande novità. Poter compilare lo stesso codice su più sistemi operativi da spazio a molte possibilità; il fatto che Entity Framework Core sia open-source (come d’altronde anche EF6) rende accessibile la comprensione dei suoi meccanismi più intimi, e le contribuzioni di una comunità allo sviluppo del framework ne rafforzano molto le potenzialità. Tuttavia, analizzando i cambiamenti in EFCore, si può notare che questa caratteristica rappresenta solo uno dei cambiamenti desiderati

Entity Framework (EF) Core is a lightweight, extensible, and cross-platform version of the popular Entity Framework data access technology. (Microsoft Docs)

Come si legge nella definizione, due caratteristiche fondanti di EFCore sono l’estendibilità e la leggerezza. La riscrittura di Entity Framework ha portato ad un codice meno monolitico e più modulare, che consente un maggiore disaccoppiamento tra i moduli impiegati. Lo sviluppo di EFCore, inoltre, è stato portato avanti seguendo i più moderni principi architetturali, in modo da consentire l’interazione con il framework con codice più pulito ed una migliore gestione della segregazione delle responsabilità. Infine, un altro grande obiettivo è una maggiore efficienza delle richieste inviate al database, spesso un punto critico per la responsività di un’applicazione; per questo obiettivo, il team di sviluppo EFCore sta lavorando per ottimizzare la creazione di query generate da codice LINQ.

Detto questo, la strada per raggiungere e superare il vecchio EF6 non è semplice, ed in molti casi quest’ultimo si dimostra ancora superiore. Ci sono funzionalità EF6 che ancora mancano in EFCore, ed in alcuni casi potrebbero non essere implementate affatto – la copertura di tutte le funzionalità di EF6 non è un obiettivo primario del team di sviluppo. Anche se EFCore si rivelerà sotto alcuni aspetti più evoluto ed avanzato, alcune possibilità resteranno relegate al solo framework EF6.

In EFCore, la traduzione di query LINQ in SQL ancora non è perfetta, e per casi più complessi le interrogazioni al database possono risultare meno efficienti; inoltre, per alcune casistiche particolari che coinvolgono query complesse, tuttora emergono dei bug che ne compromettono l’esatta traduzione in SQL. Il team lavora continuamente per risolvere questi problemi, spesso gestiti come issue su GitHub, segnalate spesso dagli utenti. Il framework è, quindi, in rapido miglioramento, ma ancora non del tutto immune da bug.

La domanda che sorge naturale è quindi: Entity Framework Core è un framework abbastanza maturo da essere utilizzato per una nuova applicazione?

Per poter rispondere, vediamo le principali funzionalità introdotte della nuova versione 2.1, che rappresentano un grosso passo avanti nell’evoluzione di EFCore.

Le ultime novità

Il 28 febbraio 2018 è stata rilasciata la preview della nuova versione di EFCore, la 2.1. Tra le varie nuove funzionalità, le più rilevanti sono le seguenti:

  • Lazy Loading: fino alla versione 2.0, per caricare i dati correlati ad un’entità, si doveva ricorrere o a tecniche di eager loading o di explicit loading. Se i dati derivanti dal database, contenuti nel nostro modello, sono caricati da un servizio che li traduce in DTO, il problema potrebbe non sembrare grave: per ogni richiesta al servizio, dovremmo comunque decidere a prescindere cosa caricare dalla base di dati. Questo non toglie che con il lazy loading il caricamento rimane del tutto trasparente nell’operazione di mapping, e possiamo scrivere classi più generiche che non conoscono i meccanismi intrinseci dello strato di accesso dati. L’arrivo del lazy loading, a mio parere, è un grosso passo avanti.
  • Traduzione GroupBy: piuttosto sorprendentemente, prima della versione 2.1, i GroupBy nelle query LINQ venivano eseguiti in memoria. Per molte query, tipicamente di reportistica ma non solo, questo poteva voler dire caricare intere tabelle in memoria. L’arrivo solo recente di questa funzionalità fa riflettere sulla gioventù del framework, ma almeno per questo punto il problema è stato superato.
  • Migliore generazione query: come nelle versioni precedenti, la traduzione da LINQ a SQL è stata migliorata, per quanto riguarda la risoluzione di alcuni problemi e l’efficienza del codice SQL generato. Uno dei problemi di EF, o più in generale degli ORM, è proprio la gestione di query complesse o di interrogazioni che richiedono un alto livello di efficienza, quindi migliorie in questo senso sono sempre un risultato prezioso.
  • Data seeding: nell’inizializzare un database, spesso oltre alla struttura delle tabelle è necessario inserire un insieme di dati iniziali. In EFCore 2.1, è possibile includere questi dati nelle migrazioni, in modo da portare più semplicemente la base di dati da una locazione all’altra senza script SQL aggiuntivi da eseguire.
  • Unique constraints (dalla 2.0): Più vincoli del DB riusciamo ad esprimere e a realizzare tramite un approccio code-first, migliore sarà l’integrazione con la base di dati. Recentemente in EFCore è diventato possibile specificare anche vincoli di unicità su campi diversi dalla chiave primaria. Nella dicitura impiegata si parla di AlternateKey

Vale la pena inoltre citare alcune funzionalità presenti già nelle versioni passate ma mancanti nel vecchio EF6, oltre ovviamente alla possibilità di compilare cross-platform.
Una è la possibilità di utilizzare un database provider in memoria, che consente di eseguire codice LINQ su di database virtuale, molto utile per scopi di testing e reso possibile dal disaccoppiamento della gestione del provider del DB dal resto del framework. Un altro paio di funzionalità interessanti sottolineano gli sforzi fatti per ottenere un framework più efficiente: il raggruppamento SQL di query per INSERT o UPDATE, che velocizza operazioni di scrittura massive, ed il pooling del contesto del database, che consente di ottimizzare l’uso degli oggetti di contesto che si connettono alla base di dati.

Alcune mancanze

Alcune funzionalità presenti in EF6 non esistono in EFCore, e non verranno necessariamente implementate in futuro. Questi punti possono rappresentare limitazioni importanti per l’uso del framework – elencati sotto, alcuni dei più rilevanti

  • Approcci non Code-First: EFCore pare molto orientato verso approcci code-first. Esistono metodi per la generazione di codice da una base di dati esistente, ma non ci sono funzioni per l’aggiornamento del modello da DB. L’approccio raccomandato è quello di creare il codice automaticamente come punto di partenza, poi di modificarlo in modo da passare ad un sistema code-first a migrazioni (il passaggio non è automatico, e può prevedere qualche operazione laboriosa). Analogamente, manca una gestione model-first, ed il supporto per un editor grafico o per file EDXML. Non è escluso che possano venir utilizzato strumenti di terze parti per queste funzionalità, ma sicuramente questo può causare difficoltà nei casi dove approcci code-first potrebbero essere poco appropriati. Al momento, l’approccio model-first sembrerebbe non essere presente nella roadmap, quindi la potremmo annoverare tra le funzionalità che non arriveranno presto.
  • Relazioni N a N senza entità di Join: attualmente, per implementare una relazione n a n, non è possibile rendere trasparente la tabella di collegamento. Questo può sporcare un po’ il codice, ma il problema potrebbe essere aggirato tramite un mapping opportuno. L’implementazione di questa funzionalità rientra nei piani del team di sviluppo di EFCore, quindi probabilmente comparirà nelle prossime versioni.
  • Mapping stored procedure SQL: le stored procedure SQL non vengono mappate a metodi C#; possono comunque venir eseguite tramite metodi che consentono il passaggio diretto di codice SQL al database. Molti sviluppatori non ne sentiranno la mancanza, ma questa assenza è un altro indizio di come EFCore potrebbe richiedere del lavoro di integrazione maggiore per ambienti database-centrici.
  • Piena funzionalità LINQ: Prima di passare a EFCore, è importante ricordarsi che la traduzione di query LINQ è tuttora considerata in-progress. Per richieste più complesse al database, potrebbero venir generate query SQL non ottimali. Per applicazioni con una gestione dati complessa, questa potrebbe essere la limitazione maggiore, ma è difficile stimare quanto potrebbe rappresentare un impedimento senza fare delle prove molto specifiche.

A queste limitazioni, va sicuramente aggiunto quanto EF6 sia un framework con molti anni di sviluppo a supporto, e che quindi può essere considerato decisamente più collaudato. Inoltre lo sviluppo del team Microsoft su EF6 non si è fermato, e tantomeno il supporto per questo framework si è interrotto o si interromperà nei prossimi anni. EF6 rimane un framework potente, solido e robusto che continuerà ad essere ampiamente utilizzato ancora per molto.

In conclusione

Vale quindi la pena di iniziare a sviluppare applicazioni con EFCore, o è meglio rimanere sul buon vecchio EF6, almeno per qualche altro anno? Sicuramente dipende dal tipo di applicazione, ma le nostre osservazioni ci possono indirizzare ad una scelta più oculata.

A mio parere le criticità sono principalmente due: la gioventù del framework, che potrebbe non gestire al meglio interrogazioni LINQ complesse, e l’assenza di un buon supporto di approcci database o model first. A questo va aggiunto che per sfruttare al meglio molte delle caratteristiche EFCore, l’ideale sarebbe quello di utilizzarlo in un’applicazione .NET Core – requisito tuttavia non indispensabile, visto che EFCore è compatibile con le versioni non-core del C#. Il passaggio a applicazioni core è sicuramente interessante, ma per un team alle prime armi questo implica tempo e impegno per imparare le diverse funzionalità per applicazioni di questo tipo. A questo va aggiunto che anche tutte le librerie utilizzate nel progetto dovrebbero essere compatibili con il framework .NET Core.

Attualmente, consiglierei di provare EFCore per nuove applicazioni che non prevedono interrogazioni dati complesse, a patto che il team di sviluppo sia a suo agio con un approccio code-first. Qualunque sia la vostra scelta, consiglio vivamente di iniziare ad interessarsi al framework, perché è su questo che le maggiori innovazioni saranno sviluppate nel prossimo periodo. Con la versione 2.1, EFCore è cresciuto molto e dà la sensazione di iniziare ad essere un prodotto maturo, pronto per la produzione. Nei prossimi anni, è possibile che le potenzialità di EFCore surclassino quelle di EF6, garantendo facilitazioni nello sviluppo non disponibili nel vecchio framework.

Un breve How-To

Per provare la nuova versione di EFCore, ho creato una piccola applicazione di test. Non ho trovato particolari difficoltà, e una volta configurato il contesto del database, l’accesso ai dati viene fatto in modo del tutto analogo ad EF6. Riporto la lista dei passaggi e dei requisiti, che vi consentirà di creare un’applicazione facente uso di EFCore in pochi minuti.

Per una piena compatibilità con progetti .NET Core, utilizzare Visual Studio 2017, con una versione pari almeno alla 15.3. Installare la .NET standard SDK, versione >= 2.0. Soddisfatti questi requisiti, è possibile creare un’applicazione .NET Core, eventualmente formata da più progetti. Nella piccola applicazione di test che ho implementato, ho creato una WebApp .NET Core ed una Class Library .NET Core per l’accesso ai dati.

A questo punto, si può procedere installando EF Core, tramite i seguenti pacchetti NuGet:

  • Microsoft.EntityFrameworkCore. Personalmente ho provato l’ultima versione in prerelease (2.1.0 preview-final) in modo da provare le ultime funzionalità
  • Occorre un provider per accedere al database, dipendente dal tipo di DB che si vuole utilizzare. Per SQL server, installare Microsoft.EntityFrameworkCore.SqlServer. Esistono provider per tutti i più diffusi tipi di base di dati, alcuni dei quali di terze parti (es. per Oracle)
  • Per abilitare le migrazioni, necessari i pacchetti Microsoft.EntityFrameworkCore.Tools e Microsoft.VisualStudio.Web.CodeGeneration.Design. Questo pacchetto va installato nel progetto di avvio della soluzione, in modo da poter leggere la stringa di connessione al DB
  • Per abilitare il Lazy Loading, installare anche Microsoft.EntityFrameworkCore.Proxies

A questo punto, si può procedere creando le classi del modello – su questo l’approccio è molto simile a EF6. Alcuni punti da tenere a mente: innanzitutto sfruttare quando possibile le convenzioni, in modo da sfruttare al meglio gli automatismi del framework. Un campo Id, per esempio, sarà riconosciuto automaticamente come chiave primaria. Secondo punto, impostare come virtuali proprietà che fanno riferimento ad altri oggetti del modello, in modo da consentire il Lazy Loading.

Il passo successivo consiste nel creare il contesto del database, estendendo la classe DbContext. Specificando le proprietà di tipo DbSet<T>, si specificano le tabelle che verranno mappate sul database. Questi oggetti possono essere interrogati tramite LINQ per generare, dietro le quinte, query SQL per l’estrazione dei dati. Per configurare l’accesso al DB, si può riscrivere il metodo OnConfiguring, oppure impiegare le funzionalità IoC integrate in .NET Core. Quest’ultimo metodo è solitamente preferibile, in quanto consente di impostare la stringa di connessione partendo dalla configurazione del progetto eseguibile. Nel file di avvio startup.cs, il metodo ConfigureServices potrebbe essere implementato come segue

public void ConfigureServices(IServiceCollection services) {
 services.AddDbContext<MyContext>(options => options
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
.UseLazyLoadingProxies()
 );
 services.AddMvc();
}

Notare che UseSqlServer è un metodo di estensione fornito del pacchetto con il provider del DB. Similarmente UseLazyLoadingProxies abilita, a run-time, l’uso del lazy loading.

Per specificare ulteriori proprietà di mapping, è possibile riscrivere il metodo OnModelCreating. Regole aggiuntive possono essere aggiunte sfruttando dichiarazioni fluent. Sotto un piccolo esempio di come si può impostare una chiave composita per un’entità di join, che unisce le due classi Book e Category. Avendo seguito le naming conventions, questa è l’unica regola di mapping che ho dovuto aggiungere alla mia piccola applicazione di test.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookCategory>().HasKey(bc => new { bc.BookId, bc.CategoryId });
}

Per concludere, vale la pena spendere due parole sulle migrazioni; per chi non fosse familiare con questo concetto, le migrazioni sono un sistema esistente anche nel vecchio EF per la gestione della struttura del database. Queste consentono di creare automaticamente delle classi C# che aggiornino lo schema del database coerentemente con il nostro codice, effettuandone inizialmente la creazione, e dando la possibilità di passare da una versione alla successiva o viceversa.

Per creare migrazioni, e divenire così in grado di portare comodamente la struttura del database su ambienti diversi, utilizzare la console powershell. Il progetto di destinazione da selezionare è quello dove risiede la classe con il contesto del database. Il progetto eseguibile deve contenere la stringa di connessione ed avere il pacchetto Microsoft.VisualStudio.Web.CodeGeneration.Design. Dalla console, il comando Add-migration crea una nuova migrazione, ed il comando Update-Database applica i cambiamenti al database. Anche su questo il funzionamento di EFCore è del tutto analogo a quello di EF6. È però interessante notare una funzionalità aggiuntiva di EFCore: oltre alle migrazioni incrementali, viene creato anche un file contenente lo snapshot dell’intera struttura del DB, utile per avere rapidamente una visione complessiva di tutta la base di dati. Quando viene creata una nuova migrazione, EFCore sarà in grado di capire i cambiamenti da quella vecchia esaminando questo file, senza aver bisogno di accedere fisicamente al database.

A questo punto, dovreste essere pronti a provare la vostra applicazione con EF Core! Utilizzando lo scaffolding di Visual Studio, potete creare rapidamente controller e viste per interagire con le classi del modello e verificarne il corretto funzionamento.

RISORSE ONLINE

Riporto una lista di risorse che mi sento di raccomandare per approfondire la conoscenza di EF Core

Corsi pluralsight
Richiedono una sottoscrizione. Questi corsi descrivono le novità di EFCore (versioni 1.0 e 2.0) e come iniziare ad utilizzare questo framework.
https://www.pluralsight.com/courses/entity-framework-core-getting-started
https://www.pluralsight.com/courses/entity-framework-core-2-getting-started

Eager, explicit, lazy loading
Documentazione ufficiale sui tre diversi tipi di caricamento delle entità correlate
https://docs.microsoft.com/en-us/ef/core/querying/related-data

EF Core vs EF6
Una lista completa di tutte le funzionalità presenti nei due framework. Da consultare prima di iniziare un progetto in EFCore, in modo da assicurarsi che tutte le funzionalità necessarie siano presenti
https://docs.microsoft.com/en-us/ef/efcore-and-ef6/features

Una guida per migrazioni EFCore
In alcuni casi, la gestione delle migrazioni può riscontrare qualche problema. Sotto, un breve articolo con qualche accorgimento per far funzionare tutto al meglio.
http://thedatafarm.com/data-access/the-secret-to-running-ef-core-2-0-migrations-from-a-net-core-or-net-standard-class-library/

Un’esperienza di passaggio di un’applicazione da EF6 a EFCore
Un articolo che ho trovato interessante – utile sia per chi vuole effettuare un passaggio analogo, sia per un ulteriore confronto tra EFCore ed EF6
https://www.red-gate.com/simple-talk/blogs/first-experience-migrating-net-app-core/

Configurazioni ad hoc per mapping tabelle ed entità
Un articolo con maggiori dettagli su come definire mapping personalizzati per le entità del modello
https://scottsauber.com/2017/09/11/customizing-ef-core-2-0-with-ientitytypeconfiguration/

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *