Le novità di Entity Framework Core 6

di Stefano Mostarda, in Entity Framework Core,

Entity Framework Core 6 (EF Core 6 d'ora in poi) è una versione molto matura del framework. Rispetto alle versioni precedenti, questa non introduce grosse novità, ma solo tanti piccoli miglioramenti. Le principali novità di EF Core 6 sono le seguenti:

  • ottimizzazione delle performance;
  • supporto alle temporal table di Sql Server;
  • aggiornamento allo strumento di scaffolding;
  • convenzioni a livello di modello;
  • Include a livello di modello;
  • precompilazione dei modelli;

In questo articolo proviamo ad analizzare meglio questi punti a partire dalle performance.

Performance

Una delle grosse pecche di Entity Framework e di EF Core è sempre stata una velocità ridotta rispetto ad altri competitor. In particolare, sebbene il paragone non fosse molto indovinato, molti hanno lamentato una notevole lentezza rispetto a Dapper. Vista la maturità ormai raggiunta dalle funzionalità e dal motore di EF Core, il team ha deciso di fare una rivisitazione delle performance intervenendo in modo non invasivo su tutte le aree del motore. Il risultato è che se EF Core 5 era più lento del 55% rispetto a Dapper (basandosi sui benchmark di TechEmpower Fortunes), EF Core 6 è ora più lento solo del 5%. Un notevole miglioramento rispetto al passato.

Un primo punto dove il team è intervenuto riguarda il pooling. Sin dalla versione 2, EF Core ha un metodo per creare un pool di oggetti DbContext e riutilizzarli invece di ricreare un'istanza ogni volta. Quando l'applicazione ha bisogno di più istanze di quante ce ne siano nel pool, le istanze vengono create e distrutte. Il limite era di 128, ma il team ha alzato questo limite a 1024 notando un incremento delle performance intorno al 5%.

Il secondo punto su cui il team è intervenuto è il logging. Inviare ogni volta le informazioni di logging al sistema in ascolto causa l'allocazione di oggetti e l'esecuzione di codice spesso inutile quando non ci sono sistemi in ascolto. Per questo motivo il team ha deciso di congelare la configurazione di logging per un secondo. Passato il secondo, la configurazione viene di nuovo letta (per eventuali cambiamenti a runtime) e congelata per un altro secondo. In questo modo, se l'applicazione non ha logging, per un secondo tutti i check effettuati a runtime vengono saltati e il codice diventa molto più veloce. Se dopo mezzo secondo si abilità il logging, questo rimarrà disabilitato per mezzo secondo e poi verrà abilitato. La configurazione verrà di nuovo congelata e i check rimarranno quelli in memoria e non quelli da configurazione. Il tempo di un secondo è configurabile attraverso la proprietà ConfigureLoggingCacheTime nelle opzioni del contesto.

Il terzo punto su cui il team è intervenuto è nel riutilizzo di oggetti. Quando EF Core esegue una query, internamente utilizza i vari DbConnection, DbCommand, DbDataReader e così via. Fino a EF Core 5, questi oggetti venivano creati e distrutti a ogni comando, EF Core 6 invece crea questi oggetti la prima volta e poi, invece di distruggerli, li tiene nell'istanza del DbContext per riutilizzarli successivamente (questo a patto che usiamo il pooling dei DbContext).

Un altro punto importante che ha permesso di guadagnare in performance è il thread safety check. EF Core non è thread-safe, e per rinforzare questa policy, cerca di evidenziare un eventuale utilizzo cross-thread sollevando un'eccezione. Per capire se sollevare un'eccezione di questo tipo, EF Core esegue dei controlli dispendiosi e la loro disabilitazione comporta un buon aumento delle performance. Se siamo sicuri di non utilizzare mai un'istanza del DbContext da più thread, possiamo disabilitare i controlli con il metodo EnableThreadSafetyChecks(false) nelle opzioni.

Essendo queste ottimizzazioni tutte interne al framework e abilitabili in modo globale, EF Core 6 permette di ottenere questo enorme guadagno semplicemente aggiornando alla nuova versione e impostando correttamente le opzioni del DbContext. Questa caratteristica è di per se già un ottimo motivo per migrare a EF Core 6.

Temporal table e Sql Server

Dalla versione 2016, Sql Server supporta le temporal table. Si tratta di tabelle di cui Sql Server gestisce uno storico delle modifiche dei record così che in qualunque momento si possa ricostruire la storia di un determinato record con tutte le sue modifiche.

EF Core 6 supporta le temporal table sotto tutti i punti di vista: mapping, scaffolding e query. Dal punto di vista del mapping, per dichiarare una tabella come temporal, dobbiamo usare il metodo ToTable specificando il nome della tabella e una lambda di configurazione all'interno della quale chiamiamo il metodo IsTemporal come mostrato nell'esempio. Opzionalmente, possiamo anche specificare il nome delle colonne di inizio e fine validità del record e il nome della tabella contenente i dati temporali.

modelBuilder
    .Entity<Product>()
    .ToTable(
        "Products",
        b => b.IsTemporal(
            b =>
            {
                b.HasPeriodStart("PeriodStart");
                b.HasPeriodEnd("PeriodEnd");
                b.UseHistoryTable("ProductsHistory");
            }
        ));

Dal punto di vista dello scaffolding che genera il codice partendo dalle tabelle del database, il tool si accorge di quando una tabella è temporal e quindi genera in automatico il codice dell'entity e il codice di mapping in modo corretto.

Dal punto di vista della manipolazione dei dati, quando andiamo a scrivere, modificare o eliminare un record, la tabella con i dati storici viene gestita in modo trasparente da Sql Server quindi non dobbiamo modificare nulla rispetto alle tabelle non temporali. Per quanto riguarda le query, se vogliamo ritrovare i dati della tabella con i dati aggiornati, non dobbiamo fare nulla di diverso rispetto al passato, mentre la situazione cambia quando andiamo a effettuare query sui dati storici. Innanzitutto, dobbiamo usare nuovi extension method che ci danno accesso alla tabella temporale: TemporalAll, TemporalAsOf, TemporalFromTo e altri ancora. In seguito, per fare query che coinvolgono le date dobbiamo usare le colonne che contengono la data di inizio e fine validità di un record. Queste non sono esposte dalle entity, ma sono comunque esposte da EF Core come shadow property di tipo DateTime e quindi accessibili col metodo EF.Property. Combinando gli extension method precedenti con le shadow property, possiamo eseguire query sui dati storici in modo molto semplice come vedremo nelle prossime sezioni.

TemporalAll

Questo metodo è il più semplice e permette di accedere a tutti i record della tabella storica.

var customerSnapshots = context.Products
  .TemporalAll()
  .OrderBy(customer => EF.Property<DateTime>(customer, "PeriodStart"))
  .Select(customer =>
    new
    {
      Product = product,
      PeriodStart = EF.Property<DateTime>(customer, "PeriodStart"),
      PeriodEnd = EF.Property<DateTime>(customer, "PeriodEnd")
    })
  .ToList();

In questo caso, abbiamo recuperato tutti i record storici dei prodotti e li abbiamo ordinati per data di inizio validità selezionando per ogni record il prodotto e la data inizio e fine validità.

TemporalAsOf

Questo metodo permette di accedere al valore del record a una specifica data. Se per esempio vogliamo sapere il prezzo di un prodotto a una certa data, ci basta usare TemporalAsOf passando in input la data.

var order = context.Products
  .TemporalAsOf(on)
  .First();

Dove questa tecnica mostra la sua vera potenza è quando abbiamo entity collegate e anche queste mappano su tabelle temporali. Supponiamo di avere una fattura collegata al cliente e ai prodotti, se chiediamo la fattura, EF Core automaticamente recupererà la fattura alla data di emissione e recupererà anche i dati del cliente e dei prodotti a quella data ricostruendo tutto il grafo di oggetti correttamente.

TemporalFromTo, TemporalContainedIn, TemporalBetween

Questi metodi sono meno usati rispetto ai precedenti, tuttavia coprono le restanti necessità quando si tratta di recuperare dati storici che rientrano in un range di date.

  • TemporalFromTo restituisce tutti i record attivi all'interno di un range di date;
  • TemporalContainedIn: restituisce tutti i record che sono stati attivi e hanno perso validità all'interno di un range di date;
  • TemporalBetween: restituisce tutti i record attivi all'interno di un range di date inclusi i limiti del range.
Dati cancellati

Una delle funzionalità più potenti delle temporal tables, è la capacità di preservare i dati di record che sono stati cancellati. Se inseriamo e aggiorniamo un record e infine lo cancelliamo, anche dopo la sua cancellazione i dati storici rimangono nelle temporal tables. Questo ci permette di recuperare dati non più esistenti sulle tabelle principali, ma comunque esistenti a una certa data. Questa caratteristica può rimpiazzare in molti casi la cosiddetta cancellazione logica.

2 pagine in totale: 1 2
Contenuti dell'articolo

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

Top Ten Articoli

Articoli via e-mail

Iscriviti alla nostra newsletter nuoviarticoli per ricevere via e-mail le notifiche!

In primo piano

I più letti di oggi

In evidenza

Misc